diff --git a/server/common/cgo.go b/server/common/cgo.go index 4e0a6a1..efffd69 100644 --- a/server/common/cgo.go +++ b/server/common/cgo.go @@ -112,15 +112,15 @@ func (k *KvmVision) ReadH264SPS() ([]byte, int) { dataSize C.uint32_t ) - result := C.kvmv_get_sps_frame(&kvmData, &dataSize) + result := int(C.kvmv_get_sps_frame(&kvmData, &dataSize)) if result < 0 { log.Errorf("failed to read sps: %v", result) - return nil, int(result) + return nil, result } data := C.GoBytes(unsafe.Pointer(kvmData), C.int(dataSize)) - return data, int(result) + return data, result } func (k *KvmVision) ReadH264PPS() ([]byte, int) { @@ -129,14 +129,14 @@ func (k *KvmVision) ReadH264PPS() ([]byte, int) { dataSize C.uint32_t ) - result := C.kvmv_get_pps_frame(&kvmData, &dataSize) + result := int(C.kvmv_get_pps_frame(&kvmData, &dataSize)) if result < 0 { log.Errorf("failed to read pps: %v", result) - return nil, int(result) + return nil, result } data := C.GoBytes(unsafe.Pointer(kvmData), C.int(dataSize)) - return data, int(result) + return data, result } func (k *KvmVision) Close() { diff --git a/server/dl_lib/libkvm.so b/server/dl_lib/libkvm.so index 47b7611..68045f9 100644 Binary files a/server/dl_lib/libkvm.so and b/server/dl_lib/libkvm.so differ diff --git a/server/go.mod b/server/go.mod index cc3a566..7637c82 100644 --- a/server/go.mod +++ b/server/go.mod @@ -14,7 +14,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.19.0 github.com/unrolled/secure v1.15.0 - golang.org/x/crypto v0.28.0 + golang.org/x/crypto v0.31.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -70,9 +70,9 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/server/go.sum b/server/go.sum index a34a695..9f44074 100644 --- a/server/go.sum +++ b/server/go.sum @@ -158,19 +158,28 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/server/include/kvm_vision.h b/server/include/kvm_vision.h index b8b97e0..2fa2a25 100644 --- a/server/include/kvm_vision.h +++ b/server/include/kvm_vision.h @@ -1,3 +1,5 @@ + + #ifndef KVM_VISION_H_ #define KVM_VISION_H_ @@ -51,6 +53,7 @@ int free_kvmv_data(uint8_t ** _pp_kvm_data); void free_all_kvmv_data(); void set_h264_gop(uint8_t _gop); void kvmv_deinit(); +uint8_t kvmv_hdmi_control(uint8_t _en); #ifdef __cplusplus } diff --git a/server/main.go b/server/main.go index 97d87df..4dbdee7 100644 --- a/server/main.go +++ b/server/main.go @@ -6,6 +6,7 @@ import ( "NanoKVM-Server/logger" "NanoKVM-Server/middleware" "NanoKVM-Server/router" + "NanoKVM-Server/utils" "fmt" "os" "os/signal" @@ -32,8 +33,11 @@ func main() { func initialize() { logger.Init() + _ = common.GetScreen() _ = common.GetKvmVision() + + utils.InitGoMemLimit() } func run(r *gin.Engine) { diff --git a/server/proto/application.go b/server/proto/application.go index f2fa841..7161e4e 100644 --- a/server/proto/application.go +++ b/server/proto/application.go @@ -4,7 +4,3 @@ type GetVersionRsp struct { Current string `json:"current"` Latest string `json:"latest"` } - -type GetLibRsp struct { - Exist bool `json:"exist"` -} diff --git a/server/proto/auth.go b/server/proto/auth.go index 61ec69f..6c4f28c 100644 --- a/server/proto/auth.go +++ b/server/proto/auth.go @@ -9,6 +9,10 @@ type LoginRsp struct { Token string `json:"token"` } +type GetAccountRsp struct { + Username string `json:"username"` +} + type ChangePasswordReq struct { Username string `json:"username" validate:"required"` Password string `json:"password" validate:"required"` diff --git a/server/proto/network.go b/server/proto/network.go index a8b3eb2..336346f 100644 --- a/server/proto/network.go +++ b/server/proto/network.go @@ -12,24 +12,24 @@ type DeleteMacReq struct { Mac string `form:"mac" validate:"required"` } +type TailscaleState string + +const ( + TailscaleNotInstall TailscaleState = "notInstall" + TailscaleNotLogin TailscaleState = "notLogin" + TailscaleStopped TailscaleState = "stopped" + TailscaleRunning TailscaleState = "running" +) + type GetTailscaleStatusRsp struct { - Status string `json:"status"` // notInstall | notLogin | stopped | running - Name string `json:"name"` - IP string `json:"ip"` - Account string `json:"account"` -} - -type UpdateTailscaleStatusReq struct { - Command string // up | down -} - -type UpdateTailscaleStatusRsp struct { - Status string `json:"status"` // stopped | running + State TailscaleState `json:"state"` + Name string `json:"name"` + IP string `json:"ip"` + Account string `json:"account"` } type LoginTailscaleRsp struct { - Status string `json:"status"` - Url string `json:"url"` + Url string `json:"url"` } type GetWifiRsp struct { diff --git a/server/proto/vm.go b/server/proto/vm.go index 14b76eb..72d0d87 100644 --- a/server/proto/vm.go +++ b/server/proto/vm.go @@ -56,3 +56,22 @@ type UpdateVirtualDeviceReq struct { type UpdateVirtualDeviceRsp struct { On bool `json:"on"` } + +type SetMemoryLimitReq struct { + Enabled bool `json:"enabled"` + Limit int64 `json:"limit"` +} + +type GetMemoryLimitRsp struct { + Enabled bool `json:"enabled"` + Limit int64 `json:"limit"` +} + +type SetOledReq struct { + Sleep int `json:"sleep"` +} + +type GetOLEDRsp struct { + Exist bool `json:"exist"` + Sleep int `json:"sleep"` +} diff --git a/server/router/auth.go b/server/router/auth.go index e19044e..5115a92 100644 --- a/server/router/auth.go +++ b/server/router/auth.go @@ -10,11 +10,11 @@ import ( func authRouter(r *gin.Engine) { service := auth.NewService() - r.POST("/api/auth/login", service.Login) // login - r.POST("/api/auth/wifi", service.ConnectWifi) // connect Wi-Fi + r.POST("/api/auth/login", service.Login) // login api := r.Group("/api").Use(middleware.CheckToken()) api.GET("/auth/password", service.IsPasswordUpdated) // is password updated + api.GET("/auth/account", service.GetAccount) // get account api.POST("/auth/password", service.ChangePassword) // change password } diff --git a/server/router/network.go b/server/router/network.go index 8807c1f..667ad5e 100644 --- a/server/router/network.go +++ b/server/router/network.go @@ -9,17 +9,22 @@ import ( func networkRouter(r *gin.Engine) { service := network.NewService() + + r.POST("/api/network/wifi", service.ConnectWifi) // connect Wi-Fi + api := r.Group("/api").Use(middleware.CheckToken()) api.POST("/network/wol", service.WakeOnLAN) // wake on lan api.GET("/network/wol/mac", service.GetMac) // get mac list api.DELETE("/network/wol/mac", service.DeleteMac) // delete mac - api.POST("/network/tailscale/install", service.InstallTailscale) // install tailscale - api.GET("/network/tailscale/status", service.GetTailscaleStatus) // get tailscale status - api.POST("/network/tailscale/status", service.UpdateTailscaleStatus) // update tailscale status - api.POST("/network/tailscale/login", service.LoginTailscale) // tailscale login - api.POST("/network/tailscale/logout", service.LogoutTailscale) // tailscale logout + api.POST("/network/tailscale/install", service.TsInstall) // install tailscale + api.POST("/network/tailscale/uninstall", service.TsUninstall) // uninstall tailscale + api.GET("/network/tailscale/status", service.GetTsStatus) // get tailscale status + api.POST("/network/tailscale/up", service.TsUp) // run tailscale up + api.POST("/network/tailscale/down", service.TsDown) // run tailscale down + api.POST("/network/tailscale/login", service.TsLogin) // tailscale login + api.POST("/network/tailscale/logout", service.TsLogout) // tailscale logout - api.GET("/network/wifi", service.GetWifi) // get wifi information + api.GET("/network/wifi", service.GetWifi) // get Wi-Fi information } diff --git a/server/router/vm.go b/server/router/vm.go index 7efc101..38adda4 100644 --- a/server/router/vm.go +++ b/server/router/vm.go @@ -26,4 +26,10 @@ func vmRouter(r *gin.Engine) { api.GET("/vm/device/virtual", service.GetVirtualDevice) // get virtual device api.POST("/vm/device/virtual", service.UpdateVirtualDevice) // update virtual device + + api.GET("/vm/memory/limit", service.GetMemoryLimit) // get memory limit + api.POST("/vm/memory/limit", service.SetMemoryLimit) // set memory limit + + api.GET("/vm/oled", service.GetOLED) // get OLED configuration + api.POST("/vm/oled", service.SetOLED) // set OLED configuration } diff --git a/server/service/auth/login.go b/server/service/auth/login.go index 29da0d5..7814359 100644 --- a/server/service/auth/login.go +++ b/server/service/auth/login.go @@ -41,7 +41,7 @@ func (s *Service) Login(c *gin.Context) { return } - account, err := getAccount() + account, err := utils.GetAccount() if err != nil { rsp.ErrRsp(c, -3, "get account failed") return @@ -65,6 +65,21 @@ func (s *Service) Login(c *gin.Context) { log.Debugf("login success, username: %s", req.Username) } +func (s *Service) GetAccount(c *gin.Context) { + var rsp proto.Response + + account, err := utils.GetAccount() + if err != nil { + rsp.ErrRsp(c, -1, "get account failed") + return + } + + rsp.OkRspWithData(c, &proto.GetAccountRsp{ + Username: account.Username, + }) + log.Debugf("get account successful") +} + func isLibExist() bool { libPath := fmt.Sprintf("/kvmapp/kvm_system/dl_lib/libmaixcam_lib.so") _, err := os.Stat(libPath) diff --git a/server/service/auth/password.go b/server/service/auth/password.go index a12179f..98ff20b 100644 --- a/server/service/auth/password.go +++ b/server/service/auth/password.go @@ -2,6 +2,11 @@ package auth import ( "NanoKVM-Server/proto" + "NanoKVM-Server/utils" + "io" + "os" + "os/exec" + "time" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" @@ -16,8 +21,23 @@ func (s *Service) ChangePassword(c *gin.Context) { return } - if err := setAccount(req.Username, req.Password); err != nil { - rsp.ErrRsp(c, -2, "change password failed") + err := utils.SetAccount(req.Username, req.Password) + if err != nil { + rsp.ErrRsp(c, -2, "failed to save password") + return + } + + account, err := utils.GetAccount() + if err != nil { + rsp.ErrRsp(c, -3, "failed to get password") + return + } + + // change root password + err = changeRootPassword(account.Password) + if err != nil { + _ = utils.DelAccount() + rsp.ErrRsp(c, -4, "failed to change password") return } @@ -28,18 +48,68 @@ func (s *Service) ChangePassword(c *gin.Context) { func (s *Service) IsPasswordUpdated(c *gin.Context) { var rsp proto.Response - account, err := getAccount() - if err != nil { - rsp.ErrRsp(c, -1, "failed to get password") - } + isUpdated := false - isUpdated := true - if account == nil || account.Password == "admin" { - isUpdated = false + if utils.IsAccountExist() { + account, err := utils.GetAccount() + if err != nil { + rsp.ErrRsp(c, -1, "failed to get password") + return + } + + if account != nil && account.Password != "admin" { + isUpdated = true + } } rsp.OkRspWithData(c, &proto.IsPasswordUpdatedRsp{ IsUpdated: isUpdated, }) - log.Debugf("get password success") + log.Debugf("is password updated: %t", isUpdated) +} + +func changeRootPassword(password string) error { + err := passwd(password) + if err != nil { + log.Errorf("failed to change root password: %s", err) + return err + } + + log.Debugf("change root password successful.") + return nil +} + +func passwd(password string) error { + cmd := exec.Command("passwd", "root") + + stdin, err := cmd.StdinPipe() + if err != nil { + return err + } + defer func() { + _ = stdin.Close() + }() + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err = cmd.Start(); err != nil { + return err + } + + if _, err = io.WriteString(stdin, password+"\n"); err != nil { + return err + } + + time.Sleep(100 * time.Millisecond) + + if _, err = io.WriteString(stdin, password+"\n"); err != nil { + return err + } + + if err = cmd.Wait(); err != nil { + return err + } + + return nil } diff --git a/server/service/auth/wifi.go b/server/service/auth/wifi.go deleted file mode 100644 index 3c80510..0000000 --- a/server/service/auth/wifi.go +++ /dev/null @@ -1,46 +0,0 @@ -package auth - -import ( - "NanoKVM-Server/proto" - "os" - - "github.com/gin-gonic/gin" - log "github.com/sirupsen/logrus" -) - -const ( - WiFiSSID = "/etc/kvm/wifi.ssid" - WiFiPasswd = "/etc/kvm/wifi.pass" - WiFiConnect = "/kvmapp/kvm/wifi_try_connect" -) - -func (s *Service) ConnectWifi(c *gin.Context) { - var req proto.ConnectWifiReq - var rsp proto.Response - - if err := proto.ParseFormRequest(c, &req); err != nil { - rsp.ErrRsp(c, -1, "invalid parameters") - return - } - - if err := os.WriteFile(WiFiSSID, []byte(req.Ssid), 0o644); err != nil { - log.Errorf("failed to save wifi ssid: %s", err) - rsp.ErrRsp(c, -2, "failed to save wifi") - return - } - - if err := os.WriteFile(WiFiPasswd, []byte(req.Password), 0o644); err != nil { - log.Errorf("failed to save wifi password: %s", err) - rsp.ErrRsp(c, -3, "failed to save wifi") - return - } - - if err := os.WriteFile(WiFiConnect, nil, 0o644); err != nil { - log.Errorf("failed to connect wifi: %s", err) - rsp.ErrRsp(c, -4, "failed to connect wifi") - return - } - - rsp.OkRsp(c) - log.Debugf("set wifi successfully: %s", req.Ssid) -} diff --git a/server/service/hid/reset.go b/server/service/hid/reset.go index 703448f..b876442 100644 --- a/server/service/hid/reset.go +++ b/server/service/hid/reset.go @@ -3,6 +3,7 @@ package hid import ( "NanoKVM-Server/proto" "os" + "time" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" @@ -43,6 +44,8 @@ func (s *Service) Reset(c *gin.Context) { } _ = f.Close() + time.Sleep(1 * time.Second) + devices, err := os.ReadDir("/sys/class/udc/") if err != nil { log.Errorf("read udc directory failed: %s", err) diff --git a/server/service/network/service.go b/server/service/network/service.go index 6b02b6b..2676f43 100644 --- a/server/service/network/service.go +++ b/server/service/network/service.go @@ -1,7 +1,41 @@ package network +import ( + "NanoKVM-Server/service/network/tailscale" + + "github.com/gin-gonic/gin" +) + type Service struct{} func NewService() *Service { return &Service{} } + +func (s *Service) TsInstall(c *gin.Context) { + tailscale.Install(c) +} + +func (s *Service) TsUninstall(c *gin.Context) { + tailscale.Uninstall(c) +} + +func (s *Service) GetTsStatus(c *gin.Context) { + tailscale.GetStatus(c) +} + +func (s *Service) TsUp(c *gin.Context) { + tailscale.Up(c) +} + +func (s *Service) TsDown(c *gin.Context) { + tailscale.Down(c) +} + +func (s *Service) TsLogin(c *gin.Context) { + tailscale.Login(c) +} + +func (s *Service) TsLogout(c *gin.Context) { + tailscale.Logout(c) +} diff --git a/server/service/network/tailscale.go b/server/service/network/tailscale.go deleted file mode 100644 index deef33e..0000000 --- a/server/service/network/tailscale.go +++ /dev/null @@ -1,322 +0,0 @@ -package network - -import ( - "bufio" - "encoding/json" - "fmt" - "io" - "net" - "net/http" - "os" - "os/exec" - "regexp" - "strings" - - "github.com/gin-gonic/gin" - log "github.com/sirupsen/logrus" - - "NanoKVM-Server/proto" - "NanoKVM-Server/utils" -) - -const ( - tailscalePath = "/usr/bin/tailscale" - tailscaledPath = "/usr/sbin/tailscaled" - - backendStateRunning = "Running" - backendStateNeedsLogin = "NeedsLogin" - backendStateStopped = "Stopped" - - responseStateRunning = "running" - responseStateNeedsLogin = "notLogin" - responseStateStopped = "stopped" - responseStateNotInstalled = "notInstall" -) - -var backendToResponseStates = map[string]string{ - backendStateRunning: responseStateRunning, - backendStateNeedsLogin: responseStateNeedsLogin, - backendStateStopped: responseStateStopped, -} - -func stateToJson(state string) string { - if resp, ok := backendToResponseStates[state]; ok { - return resp - } - return "" -} - -type TailscaleStatus struct { - BackendState string `json:"BackendState"` - - Self struct { - HostName string `json:"HostName"` - TailscaleIPs []string `json:"TailscaleIPs"` - } `json:"Self"` - - CurrentTailnet struct { - Name string `json:"Name"` - } `json:"CurrentTailnet"` -} - -func (s *Service) InstallTailscale(c *gin.Context) { - var rsp proto.Response - - if exist := isTailscaleExist(); exist { - rsp.OkRsp(c) - return - } - - const ( - downloadUrl = "https://cdn.sipeed.com/nanokvm/resources/tailscale_riscv64.zip" - workspace = "/root/.tailscale" - ) - - var ( - zipFile = fmt.Sprintf("%s/tailscale_riscv64.zip", workspace) - tailscale = fmt.Sprintf("%s/tailscale_riscv64/tailscale", workspace) - tailscaled = fmt.Sprintf("%s/tailscale_riscv64/tailscaled", workspace) - ) - - // download - _ = os.MkdirAll(workspace, 0o755) - defer func() { - _ = os.RemoveAll(workspace) - }() - - req, err := http.NewRequest("GET", downloadUrl, nil) - if err != nil { - log.Errorf("failed to create new request: %s", err) - rsp.ErrRsp(c, -1, "request failed") - return - } - - err = utils.Download(req, zipFile) - if err != nil { - log.Errorf("download failed: %s", err) - rsp.ErrRsp(c, -2, "download failed") - return - } - - // install - err = utils.Unzip(zipFile, workspace) - if err != nil { - log.Errorf("unzip failed: %s", err) - rsp.ErrRsp(c, -3, "unzip failed") - return - } - - err = utils.MoveFile(tailscale, tailscalePath) - if err != nil { - log.Errorf("rename %s failed: %s", tailscale, err) - } - err = utils.MoveFile(tailscaled, tailscaledPath) - if err != nil { - log.Errorf("rename %s failed: %s", tailscaled, err) - } - - _ = runTailscale() - - rsp.OkRsp(c) - log.Debugf("install tailscaled success") -} - -func (s *Service) LoginTailscale(c *gin.Context) { - var rsp proto.Response - - status, err := getTailscaleStatus() - if err != nil { - _ = runTailscale() - status, err = getTailscaleStatus() - } - - if err != nil { - rsp.ErrRsp(c, -1, "tailscale unknown status") - return - } - - if status.BackendState == backendStateRunning { - rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{ - Status: responseStateRunning, - }) - return - } - - cmd := exec.Command("sh", "-c", "tailscale login --timeout=10m") - stderr, err := cmd.StderrPipe() - if err != nil { - rsp.ErrRsp(c, -2, "tailscale login failed") - return - } - - defer func() { - _ = stderr.Close() - }() - - go func() { - _ = cmd.Run() - }() - - url := parseLoginUrl(stderr) - rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{ - Status: responseStateNeedsLogin, - Url: url, - }) - log.Debugf("tailscale login url: %s", url) -} - -func (s *Service) LogoutTailscale(c *gin.Context) { - var rsp proto.Response - - cmd := exec.Command("sh", "-c", "tailscale logout") - err := cmd.Run() - if err != nil { - rsp.ErrRsp(c, -2, "tailscale logout failed") - return - } - - rsp.OkRsp(c) - log.Debugf("tailscale logout success") -} - -func (s *Service) GetTailscaleStatus(c *gin.Context) { - var rsp proto.Response - var data proto.GetTailscaleStatusRsp - - if exist := isTailscaleExist(); !exist { - data.Status = responseStateNotInstalled - rsp.OkRspWithData(c, data) - return - } - - status, err := getTailscaleStatus() - if err != nil { - data.Status = responseStateNeedsLogin - rsp.OkRspWithData(c, data) - return - } - - data.Status = stateToJson(status.BackendState) - if data.Status == "" { - rsp.ErrRsp(c, -1, "unknown state") - return - } - - for _, tailscaleIp := range status.Self.TailscaleIPs { - ip := net.ParseIP(tailscaleIp) - if ip != nil && ip.To4() != nil { - data.IP = ip.String() - } - } - - data.Name = status.Self.HostName - data.Account = status.CurrentTailnet.Name - - rsp.OkRspWithData(c, data) - log.Debugf("tailscale status: %s", data.Status) -} - -func (s *Service) UpdateTailscaleStatus(c *gin.Context) { - var req proto.UpdateTailscaleStatusReq - var rsp proto.Response - - if err := proto.ParseFormRequest(c, &req); err != nil { - rsp.ErrRsp(c, -1, "invalid arguments") - return - } - - command := fmt.Sprintf("tailscale %s", req.Command) - err := exec.Command("sh", "-c", command).Run() - if err != nil { - msg := fmt.Sprintf("tailscale %s failed", req.Command) - rsp.ErrRsp(c, -2, msg) - return - } - - status, err := getTailscaleStatus() - if err != nil { - rsp.ErrRsp(c, -3, "get tailscale status failed") - return - } - - data := &proto.UpdateTailscaleStatusRsp{} - - switch status.BackendState { - case backendStateRunning: - data.Status = responseStateRunning - case backendStateStopped: - data.Status = responseStateStopped - default: - rsp.ErrRsp(c, -4, "unknown tailscale status") - return - } - - rsp.OkRspWithData(c, data) - log.Debugf("tailscale %s success", req.Command) -} - -func isTailscaleExist() bool { - _, err1 := os.Stat(tailscalePath) - _, err2 := os.Stat(tailscaledPath) - - return err1 == nil && err2 == nil -} - -func runTailscale() error { - for _, filePath := range []string{tailscalePath, tailscaledPath} { - if err := utils.EnsurePermission(filePath, 0o100); err != nil { - return err - } - } - - err := exec.Command("sh", "-c", "/etc/init.d/S98tailscaled start").Run() - if err != nil { - return err - } - - return nil -} - -func getTailscaleStatus() (TailscaleStatus, error) { - var status TailscaleStatus - - command := "tailscale status --json" - cmd := exec.Command("sh", "-c", command) - output, err := cmd.CombinedOutput() - if err != nil { - log.Debugf("get tailscale status failed: %s", err) - return status, err - } - - // delete warning message - if str := string(output); !strings.HasPrefix(str, "{") { - index := strings.Index(str, "{") - if index != -1 { - output = []byte(str[index:]) - } - } - - err = json.Unmarshal(output, &status) - if err != nil { - log.Debugf("unmarshal tailscale status failed: %s", err) - return status, err - } - - return status, nil -} - -func parseLoginUrl(r io.Reader) string { - reader := bufio.NewReader(r) - for { - line, err := reader.ReadString('\n') - if err != nil { - log.Errorf("reading line failed: %s", err) - return "" - } - - if strings.Contains(line, "https") { - reg := regexp.MustCompile(`\s+`) - return reg.ReplaceAllString(line, "") - } - } -} diff --git a/server/service/network/tailscale/account.go b/server/service/network/tailscale/account.go new file mode 100644 index 0000000..beb6cc1 --- /dev/null +++ b/server/service/network/tailscale/account.go @@ -0,0 +1,68 @@ +package tailscale + +import ( + "NanoKVM-Server/proto" + "NanoKVM-Server/utils" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +func Login(c *gin.Context) { + var rsp proto.Response + + // check tailscale status + cli := NewCli() + status, err := cli.Status() + if err != nil { + _ = cli.Start() + status, err = cli.Status() + } + + if err != nil { + log.Errorf("failed to get tailscale status: %s", err) + rsp.ErrRsp(c, -1, "unknown status") + return + } + + if status.BackendState == "Running" { + rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{}) + return + } + + // get login url + url, err := cli.Login() + if err != nil { + log.Errorf("failed to run tailscale login: %s", err) + rsp.ErrRsp(c, -2, "login failed") + return + } + + // set GOMEMLIMIT = 50M + if !utils.IsGoMemLimitExist() { + _ = utils.SetGoMemLimit(50) + } + + rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{ + Url: url, + }) + + log.Debugf("tailscale login url: %s", url) +} + +func Logout(c *gin.Context) { + var rsp proto.Response + + err := NewCli().Logout() + if err != nil { + rsp.ErrRsp(c, -1, "logout failed") + log.Errorf("failed to run tailscale logout: %s", err) + return + } + + // delete GOMEMLIMIT + _ = utils.DelGoMemLimit() + + rsp.OkRsp(c) + log.Debugf("tailscale logout successfully") +} diff --git a/server/service/network/tailscale/cli.go b/server/service/network/tailscale/cli.go new file mode 100644 index 0000000..f43201e --- /dev/null +++ b/server/service/network/tailscale/cli.go @@ -0,0 +1,120 @@ +package tailscale + +import ( + "NanoKVM-Server/utils" + "bufio" + "encoding/json" + "errors" + "os/exec" + "regexp" + "strings" +) + +type Cli struct{} + +type TsStatus struct { + BackendState string `json:"BackendState"` + + Self struct { + HostName string `json:"HostName"` + TailscaleIPs []string `json:"TailscaleIPs"` + } `json:"Self"` + + CurrentTailnet struct { + Name string `json:"Name"` + } `json:"CurrentTailnet"` +} + +func NewCli() *Cli { + return &Cli{} +} + +func (c *Cli) Start() error { + for _, filePath := range []string{TailscalePath, TailscaledPath} { + if err := utils.EnsurePermission(filePath, 0o100); err != nil { + return err + } + } + + command := "/etc/init.d/S98tailscaled start" + return exec.Command("sh", "-c", command).Run() +} + +func (c *Cli) Stop() error { + command := "/etc/init.d/S98tailscaled stop" + return exec.Command("sh", "-c", command).Run() +} + +func (c *Cli) Up() error { + command := "tailscale up" + return exec.Command("sh", "-c", command).Run() +} + +func (c *Cli) Down() error { + command := "tailscale down" + return exec.Command("sh", "-c", command).Run() +} + +func (c *Cli) Status() (*TsStatus, error) { + command := "tailscale status --json" + cmd := exec.Command("sh", "-c", command) + + output, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + + // output is not in standard json format + if outputStr := string(output); !strings.HasPrefix(outputStr, "{") { + index := strings.Index(outputStr, "{") + if index == -1 { + return nil, errors.New("unknown output") + } + + output = []byte(outputStr[index:]) + } + + var status TsStatus + err = json.Unmarshal(output, &status) + if err != nil { + return nil, err + } + + return &status, nil +} + +func (c *Cli) Login() (string, error) { + command := "tailscale login --timeout=10m" + cmd := exec.Command("sh", "-c", command) + + stderr, err := cmd.StderrPipe() + if err != nil { + return "", err + } + defer func() { + _ = stderr.Close() + }() + + go func() { + _ = cmd.Run() + }() + + reader := bufio.NewReader(stderr) + for { + line, err := reader.ReadString('\n') + if err != nil { + return "", err + } + + if strings.Contains(line, "https") { + reg := regexp.MustCompile(`\s+`) + url := reg.ReplaceAllString(line, "") + return url, nil + } + } +} + +func (c *Cli) Logout() error { + command := "tailscale logout" + return exec.Command("sh", "-c", command).Run() +} diff --git a/server/service/network/tailscale/install.go b/server/service/network/tailscale/install.go new file mode 100644 index 0000000..4174b7b --- /dev/null +++ b/server/service/network/tailscale/install.go @@ -0,0 +1,97 @@ +package tailscale + +import ( + "NanoKVM-Server/proto" + "NanoKVM-Server/utils" + "fmt" + "net/http" + "os" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + DownloadUrl = "https://cdn.sipeed.com/nanokvm/resources/tailscale_riscv64.zip" + Workspace = "/root/.tailscale" + + TailscalePath = "/usr/bin/tailscale" + TailscaledPath = "/usr/sbin/tailscaled" +) + +func Install(c *gin.Context) { + var rsp proto.Response + + if IsInstalled() { + rsp.OkRsp(c) + return + } + + _ = os.MkdirAll(Workspace, 0o755) + defer func() { + _ = os.RemoveAll(Workspace) + }() + + // download + req, err := http.NewRequest("GET", DownloadUrl, nil) + if err != nil { + rsp.ErrRsp(c, -1, "request failed") + log.Errorf("failed to create request: %s", err) + return + } + + zipPath := fmt.Sprintf("%s/tailscale_riscv64.zip", Workspace) + err = utils.Download(req, zipPath) + if err != nil { + rsp.ErrRsp(c, -2, "download failed") + log.Errorf("failed to download tailscale: %s", err) + return + } + + // install + err = utils.Unzip(zipPath, Workspace) + if err != nil { + rsp.ErrRsp(c, -3, "unzip failed") + log.Errorf("failed to unzip tailscale: %s", err) + return + } + + tailscalePath := fmt.Sprintf("%s/tailscale_riscv64/tailscale", Workspace) + err = utils.MoveFile(tailscalePath, TailscalePath) + if err != nil { + rsp.ErrRsp(c, -4, "install failed") + log.Errorf("failed to move tailscale: %s", err) + return + } + + tailscaledPath := fmt.Sprintf("%s/tailscale_riscv64/tailscaled", Workspace) + err = utils.MoveFile(tailscaledPath, TailscaledPath) + if err != nil { + rsp.ErrRsp(c, -5, "install failed") + log.Errorf("failed to move tailscaled: %s", err) + return + } + + rsp.OkRsp(c) + log.Debugf("install tailscale successfully") +} + +func Uninstall(c *gin.Context) { + var rsp proto.Response + + _ = NewCli().Stop() + _ = utils.DelGoMemLimit() + + _ = os.Remove(TailscalePath) + _ = os.Remove(TailscaledPath) + + rsp.OkRsp(c) + log.Debugf("uninstall tailscale successfully") +} + +func IsInstalled() bool { + _, err1 := os.Stat(TailscalePath) + _, err2 := os.Stat(TailscaledPath) + + return err1 == nil && err2 == nil +} diff --git a/server/service/network/tailscale/status.go b/server/service/network/tailscale/status.go new file mode 100644 index 0000000..ccde8be --- /dev/null +++ b/server/service/network/tailscale/status.go @@ -0,0 +1,88 @@ +package tailscale + +import ( + "NanoKVM-Server/proto" + "net" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +var StateMap = map[string]proto.TailscaleState{ + "NeedsLogin": proto.TailscaleNotLogin, + "Running": proto.TailscaleRunning, + "Stopped": proto.TailscaleStopped, +} + +func GetStatus(c *gin.Context) { + var rsp proto.Response + + if !IsInstalled() { + rsp.OkRspWithData(c, &proto.GetTailscaleStatusRsp{ + State: proto.TailscaleNotInstall, + }) + return + } + + status, err := NewCli().Status() + if err != nil { + log.Debugf("failed to get tailscale status: %s", err) + rsp.OkRspWithData(c, &proto.GetTailscaleStatusRsp{ + State: proto.TailscaleNotLogin, + }) + return + } + + state, ok := StateMap[status.BackendState] + if !ok { + log.Errorf("unknown tailscale state: %s", status.BackendState) + rsp.ErrRsp(c, -1, "unknown state") + return + } + + ipv4 := "" + for _, tailscaleIp := range status.Self.TailscaleIPs { + ip := net.ParseIP(tailscaleIp) + if ip != nil && ip.To4() != nil { + ipv4 = ip.String() + } + } + + data := proto.GetTailscaleStatusRsp{ + State: state, + IP: ipv4, + Name: status.Self.HostName, + Account: status.CurrentTailnet.Name, + } + + rsp.OkRspWithData(c, &data) + log.Debugf("get tailscale status successfully") +} + +func Up(c *gin.Context) { + var rsp proto.Response + + err := NewCli().Up() + if err != nil { + rsp.ErrRsp(c, -1, "tailscale up failed") + log.Errorf("failed to run tailscale up: %s", err) + return + } + + rsp.OkRsp(c) + log.Debugf("run tailscale up successfully") +} + +func Down(c *gin.Context) { + var rsp proto.Response + + err := NewCli().Down() + if err != nil { + rsp.ErrRsp(c, -1, "tailscale down failed") + log.Errorf("failed to run tailscale down: %s", err) + return + } + + rsp.OkRsp(c) + log.Debugf("run tailscale down successfully") +} diff --git a/server/service/network/wifi.go b/server/service/network/wifi.go index f8af2f1..b2b2eb1 100644 --- a/server/service/network/wifi.go +++ b/server/service/network/wifi.go @@ -11,6 +11,9 @@ import ( const ( WiFiExistFile = "/etc/kvm/wifi_exist" + WiFiSSID = "/etc/kvm/wifi.ssid" + WiFiPasswd = "/etc/kvm/wifi.pass" + WiFiConnect = "/kvmapp/kvm/wifi_try_connect" WiFiStateFile = "/kvmapp/kvm/wifi_state" ) @@ -39,3 +42,34 @@ func (s *Service) GetWifi(c *gin.Context) { rsp.OkRspWithData(c, data) log.Debugf("get wifi state: %s", state) } + +func (s *Service) ConnectWifi(c *gin.Context) { + var req proto.ConnectWifiReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid parameters") + return + } + + if err := os.WriteFile(WiFiSSID, []byte(req.Ssid), 0o644); err != nil { + log.Errorf("failed to save wifi ssid: %s", err) + rsp.ErrRsp(c, -2, "failed to save wifi") + return + } + + if err := os.WriteFile(WiFiPasswd, []byte(req.Password), 0o644); err != nil { + log.Errorf("failed to save wifi password: %s", err) + rsp.ErrRsp(c, -3, "failed to save wifi") + return + } + + if err := os.WriteFile(WiFiConnect, nil, 0o644); err != nil { + log.Errorf("failed to connect wifi: %s", err) + rsp.ErrRsp(c, -4, "failed to connect wifi") + return + } + + rsp.OkRsp(c) + log.Debugf("set wifi successfully: %s", req.Ssid) +} diff --git a/server/service/network/wol.go b/server/service/network/wol.go index c526422..62c94a7 100644 --- a/server/service/network/wol.go +++ b/server/service/network/wol.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "github.com/gin-gonic/gin" @@ -14,7 +15,7 @@ import ( ) const ( - WolHistory = "/etc/kvm/cache/wol" + WolMacFile = "/etc/kvm/cache/wol" ) func (s *Service) WakeOnLAN(c *gin.Context) { @@ -26,34 +27,39 @@ func (s *Service) WakeOnLAN(c *gin.Context) { return } - command := fmt.Sprintf("ether-wake %s", req.Mac) + mac, err := parseMAC(req.Mac) + if err != nil { + rsp.ErrRsp(c, -2, "invalid MAC address") + return + } + + command := fmt.Sprintf("ether-wake %s", mac) cmd := exec.Command("sh", "-c", command) output, err := cmd.CombinedOutput() if err != nil { - log.Errorf("wake on lan failed: %s", err) - rsp.ErrRsp(c, -2, string(output)) + log.Errorf("failed to wake on lan: %s", err) + rsp.ErrRsp(c, -3, string(output)) return } - go saveMac(req.Mac) + go saveMac(mac) rsp.OkRsp(c) - log.Debugf("wake on lan %s success", req.Mac) + log.Debugf("wake on lan: %s", mac) } func (s *Service) GetMac(c *gin.Context) { var rsp proto.Response - content, err := os.ReadFile(WolHistory) + content, err := os.ReadFile(WolMacFile) if err != nil { rsp.ErrRsp(c, -2, "open file error") return } - macs := strings.Split(string(content), "\n") data := &proto.GetMacRsp{ - Macs: macs, + Macs: strings.Split(string(content), "\n"), } rsp.OkRspWithData(c, data) @@ -68,9 +74,9 @@ func (s *Service) DeleteMac(c *gin.Context) { return } - content, err := os.ReadFile(WolHistory) + content, err := os.ReadFile(WolMacFile) if err != nil { - log.Errorf("open %s failed: %s", WolHistory, err) + log.Errorf("failed to open %s: %s", WolMacFile, err) rsp.ErrRsp(c, -2, "read failed") return } @@ -85,15 +91,41 @@ func (s *Service) DeleteMac(c *gin.Context) { } data := strings.Join(newMacs, "\n") - err = os.WriteFile(WolHistory, []byte(data), 0o644) + err = os.WriteFile(WolMacFile, []byte(data), 0o644) if err != nil { - log.Errorf("write %s failed: %s", WolHistory, err) + log.Errorf("failed to write %s: %s", WolMacFile, err) rsp.ErrRsp(c, -3, "write failed") return } rsp.OkRsp(c) - log.Debugf("delete mac %s success", req.Mac) + log.Debugf("delete wol mac: %s", req.Mac) +} + +func parseMAC(mac string) (string, error) { + mac = strings.ToUpper(strings.TrimSpace(mac)) + + mac = strings.ReplaceAll(mac, "-", "") + mac = strings.ReplaceAll(mac, ":", "") + mac = strings.ReplaceAll(mac, ".", "") + + matched, err := regexp.MatchString("^[0-9A-F]{12}$", mac) + if err != nil { + return "", err + } + if !matched { + return "", fmt.Errorf("invalid MAC address: %s", mac) + } + + var result strings.Builder + for i := 0; i < 12; i += 2 { + if i > 0 { + result.WriteString(":") + } + result.WriteString(mac[i : i+2]) + } + + return result.String(), nil } func saveMac(mac string) { @@ -101,15 +133,15 @@ func saveMac(mac string) { return } - err := os.MkdirAll(filepath.Dir(WolHistory), 0o644) + err := os.MkdirAll(filepath.Dir(WolMacFile), 0o644) if err != nil { - log.Errorf("create dir failed: %s", err) + log.Errorf("failed to create dir: %s", err) return } - file, err := os.OpenFile(WolHistory, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + file, err := os.OpenFile(WolMacFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { - log.Errorf("open %s failed: %s", WolHistory, err) + log.Errorf("failed to open %s: %s", WolMacFile, err) return } defer func() { @@ -119,13 +151,13 @@ func saveMac(mac string) { content := fmt.Sprintf("%s\n", mac) _, err = file.WriteString(content) if err != nil { - log.Errorf("write %s failed: %s", WolHistory, err) + log.Errorf("failed to write %s: %s", WolMacFile, err) return } } func isMacExist(mac string) bool { - content, err := os.ReadFile(WolHistory) + content, err := os.ReadFile(WolMacFile) if err != nil { return false } diff --git a/server/service/stream/h264/h264.go b/server/service/stream/h264/h264.go index f7f571b..12aa481 100644 --- a/server/service/stream/h264/h264.go +++ b/server/service/stream/h264/h264.go @@ -37,7 +37,10 @@ func Connect(c *gin.Context) { config := webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ { - URLs: []string{"stun:stun.l.google.com:19302"}, + URLs: []string{ + "stun:stun.l.google.com:19302", + "stun:turn.cloudflare.com:3478", + }, }, }, } diff --git a/server/service/vm/memory.go b/server/service/vm/memory.go new file mode 100644 index 0000000..fd350f5 --- /dev/null +++ b/server/service/vm/memory.go @@ -0,0 +1,58 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "NanoKVM-Server/utils" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +func (s *Service) SetMemoryLimit(c *gin.Context) { + var req proto.SetMemoryLimitReq + var rsp proto.Response + + err := proto.ParseFormRequest(c, &req) + if err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + if req.Enabled { + err = utils.SetGoMemLimit(req.Limit) + } else { + err = utils.DelGoMemLimit() + } + + if err != nil { + rsp.ErrRsp(c, -2, "failed to set memory limit") + return + } + + rsp.OkRsp(c) + log.Debug("set memory limit successful, enabled: %t, limit: %s", req.Enabled, req.Limit) +} + +func (s *Service) GetMemoryLimit(c *gin.Context) { + var rsp proto.Response + + exist := utils.IsGoMemLimitExist() + if !exist { + rsp.OkRspWithData(c, &proto.GetMemoryLimitRsp{ + Enabled: false, + Limit: 0, + }) + return + } + + limit, err := utils.GetGoMemLimit() + if err != nil { + rsp.ErrRsp(c, -1, "failed to get memory limit") + return + } + + rsp.OkRspWithData(c, &proto.GetMemoryLimitRsp{ + Enabled: true, + Limit: limit, + }) +} diff --git a/server/service/vm/oled.go b/server/service/vm/oled.go new file mode 100644 index 0000000..495e60f --- /dev/null +++ b/server/service/vm/oled.go @@ -0,0 +1,72 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "fmt" + "os" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + OLEDExistFile = "/etc/kvm/oled_exist" + OLEDSleepFile = "/etc/kvm/oled_sleep" +) + +func (s *Service) SetOLED(c *gin.Context) { + var req proto.SetOledReq + var rsp proto.Response + + if err := proto.ParseFormRequest(c, &req); err != nil { + rsp.ErrRsp(c, -1, "invalid arguments") + return + } + + data := []byte(fmt.Sprintf("%d", req.Sleep)) + err := os.WriteFile(OLEDSleepFile, data, 0o644) + if err != nil { + rsp.ErrRsp(c, -2, "failed to write data") + return + } + + rsp.OkRsp(c) + log.Debugf("set OLED sleep: %d", req.Sleep) +} + +func (s *Service) GetOLED(c *gin.Context) { + var rsp proto.Response + + if _, err := os.Stat(OLEDExistFile); err != nil { + rsp.OkRspWithData(c, &proto.GetOLEDRsp{ + Exist: false, + Sleep: 0, + }) + return + } + + data, err := os.ReadFile(OLEDSleepFile) + if err != nil { + rsp.OkRspWithData(c, &proto.GetOLEDRsp{ + Exist: true, + Sleep: 0, + }) + return + } + + content := strings.TrimSpace(string(data)) + sleep, err := strconv.Atoi(content) + if err != nil { + log.Errorf("failed to parse OLED: %s", err) + rsp.ErrRsp(c, -1, "failed to parse OLED config") + return + } + + rsp.OkRspWithData(c, &proto.GetOLEDRsp{ + Exist: true, + Sleep: sleep, + }) + log.Debugf("get OLED config successful, sleep %d", sleep) +} diff --git a/server/service/vm/script.go b/server/service/vm/script.go index 9606e8a..d16e3ee 100644 --- a/server/service/vm/script.go +++ b/server/service/vm/script.go @@ -32,7 +32,6 @@ func (s *Service) GetScripts(c *gin.Context) { return nil }) if err != nil { - log.Errorf("get scripts failed: %s", err) rsp.ErrRsp(c, -1, "get scripts failed") return } @@ -41,7 +40,7 @@ func (s *Service) GetScripts(c *gin.Context) { Files: files, }) - log.Debugf("get scripts success") + log.Debugf("get scripts total %d", len(files)) } func (s *Service) UploadScript(c *gin.Context) { diff --git a/server/service/vm/terminal.go b/server/service/vm/terminal.go index fbf22cf..14775ed 100644 --- a/server/service/vm/terminal.go +++ b/server/service/vm/terminal.go @@ -213,6 +213,23 @@ func (s *SshClient) bridgeWSAndSSH() { <-s.closeSig } +func getRootPassword() string { + if !utils.IsAccountExist() { + return "root" + } + + account, err := utils.GetAccount() + if err != nil { + return "root" + } + + if account == nil || account.Password == "" { + return "root" + } + + return account.Password +} + func (s *Service) Terminal(c *gin.Context) { user := c.Query("u") if user == "" { @@ -221,7 +238,7 @@ func (s *Service) Terminal(c *gin.Context) { password, _ := utils.Decrypt(c.Query("t")) if password == "" { - password = "root" + password = getRootPassword() } upgrader.CheckOrigin = func(r *http.Request) bool { diff --git a/server/service/auth/account.go b/server/utils/account.go similarity index 72% rename from server/service/auth/account.go rename to server/utils/account.go index 6969cbb..80d84f3 100644 --- a/server/service/auth/account.go +++ b/server/utils/account.go @@ -1,4 +1,4 @@ -package auth +package utils import ( "encoding/json" @@ -7,8 +7,6 @@ import ( "path/filepath" log "github.com/sirupsen/logrus" - - "NanoKVM-Server/utils" ) const AccountFile = "/etc/kvm/pwd" @@ -18,7 +16,18 @@ type Account struct { Password string `json:"password"` } -func getAccount() (*Account, error) { +func IsAccountExist() bool { + if _, err := os.Stat(AccountFile); err != nil { + if errors.Is(err, os.ErrNotExist) { + return false + } + return false + } + + return true +} + +func GetAccount() (*Account, error) { // use default account if _, err := os.Stat(AccountFile); err != nil { if errors.Is(err, os.ErrNotExist) { @@ -43,7 +52,7 @@ func getAccount() (*Account, error) { return nil, err } - password, err := utils.DecodeDecrypt(account.Password) + password, err := DecodeDecrypt(account.Password) if err != nil { return nil, err } @@ -53,7 +62,7 @@ func getAccount() (*Account, error) { return &account, nil } -func setAccount(username string, password string) error { +func SetAccount(username string, password string) error { account, err := json.Marshal(&Account{ Username: username, Password: password, @@ -77,3 +86,12 @@ func setAccount(username string, password string) error { return nil } + +func DelAccount() error { + if err := os.Remove(AccountFile); err != nil { + log.Errorf("failed to delete password: %s", err) + return err + } + + return nil +} diff --git a/server/utils/memory.go b/server/utils/memory.go new file mode 100644 index 0000000..56c8047 --- /dev/null +++ b/server/utils/memory.go @@ -0,0 +1,77 @@ +package utils + +import ( + "fmt" + "os" + "runtime/debug" + "strconv" + "strings" + + log "github.com/sirupsen/logrus" +) + +const GoMemLimitFile = "/etc/kvm/GOMEMLIMIT" + +func InitGoMemLimit() { + if !IsGoMemLimitExist() { + return + } + + limit, err := GetGoMemLimit() + if err != nil { + return + } + + debug.SetMemoryLimit(limit * 1024 * 1024) + log.Debugf("set GOMEMLIMIT to %d MB", limit) +} + +func SetGoMemLimit(limit int64) error { + memoryLimit := max(limit, 50) + debug.SetMemoryLimit(memoryLimit * 1024 * 1024) + + log.Debugf("set GOMEMLIMIT to %d MB", limit) + + data := []byte(fmt.Sprintf("%d", limit)) + err := os.WriteFile(GoMemLimitFile, data, 0o644) + if err != nil { + log.Errorf("failed to write GOMEMLIMIT: %s", err) + return err + } + + return nil +} + +func GetGoMemLimit() (int64, error) { + data, err := os.ReadFile(GoMemLimitFile) + if err != nil { + log.Errorf("failed to read GOMEMLIMIT: %s", err) + return 0, err + } + + content := strings.TrimSpace(string(data)) + limit, err := strconv.ParseInt(content, 10, 64) + if err != nil { + log.Errorf("failed to parse GOMEMLIMIT: %s", err) + return 0, err + } + + return limit, nil +} + +func DelGoMemLimit() error { + debug.SetMemoryLimit(1024 * 1024 * 1024) + + err := os.Remove(GoMemLimitFile) + if err != nil { + log.Errorf("failed to delete GOMEMLIMIT: %s", err) + return err + } + + return nil +} + +func IsGoMemLimitExist() bool { + _, err := os.Stat(GoMemLimitFile) + return err == nil +} diff --git a/web/.env.development b/web/.env.development index 260fc4b..0755dc5 100644 --- a/web/.env.development +++ b/web/.env.development @@ -1,3 +1,3 @@ -VITE_SERVER_IP=192.168.1.1 +VITE_SERVER_IP=192.168.0.65 VITE_SERVER_PORT=80 VITE_WITH_CREDENTIALS=false diff --git a/web/package.json b/web/package.json index 130e803..85b8ca3 100644 --- a/web/package.json +++ b/web/package.json @@ -11,54 +11,54 @@ "preview": "vite preview" }, "dependencies": { - "@ant-design/icons": "^5.3.7", + "@ant-design/icons": "^5.5.1", "@xterm/addon-attach": "^0.11.0", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", - "antd": "^5.19.2", - "axios": "^1.7.4", + "antd": "^5.21.6", + "axios": "^1.7.7", "clsx": "^2.1.1", "crypto-js": "^4.2.0", - "i18next": "^23.11.5", - "jotai": "^2.9.0", + "i18next": "^23.16.4", + "jotai": "^2.10.1", "js-cookie": "^3.0.5", - "lucide-react": "^0.408.0", + "lucide-react": "^0.469.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-error-boundary": "^4.0.13", + "react-error-boundary": "^4.1.2", "react-helmet-async": "^2.0.5", - "react-i18next": "^14.1.2", + "react-i18next": "^14.1.3", "react-responsive": "^10.0.0", - "react-router-dom": "^6.23.1", - "react-simple-keyboard": "^3.7.124", + "react-router-dom": "^6.27.0", + "react-simple-keyboard": "^3.8.19", "semver": "^7.6.3", - "vaul": "^0.9.1", + "vaul": "^0.9.9", "websocket": "^1.0.35" }, "devDependencies": { - "@ianvs/prettier-plugin-sort-imports": "^4.2.1", + "@ianvs/prettier-plugin-sort-imports": "^4.3.1", "@types/crypto-js": "^4.2.2", "@types/js-cookie": "^3.0.6", - "@types/react": "^18.3.2", - "@types/react-dom": "^18.3.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", "@types/semver": "^7.5.8", "@types/websocket": "^1.0.10", - "@typescript-eslint/eslint-plugin": "^7.10.0", - "@typescript-eslint/parser": "^7.10.0", - "@vitejs/plugin-react": "^4.2.1", - "autoprefixer": "^10.4.19", - "eslint": "^8.57.0", + "@typescript-eslint/eslint-plugin": "^8.19.1", + "@typescript-eslint/parser": "^8.19.1", + "@vitejs/plugin-react": "^4.3.3", + "autoprefixer": "^10.4.20", + "eslint": "^9.17.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-react": "^7.34.1", - "eslint-plugin-react-hooks": "^4.6.2", - "eslint-plugin-react-refresh": "^0.4.7", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.16", "msw": "^2.6.0", - "postcss": "^8.4.38", - "prettier": "^3.2.5", - "prettier-plugin-tailwindcss": "^0.5.14", - "tailwindcss": "^3.4.13", - "typescript": "^5.4.5", - "vite": "^5.4.8", + "postcss": "^8.4.47", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.9", + "tailwindcss": "^3.4.14", + "typescript": "^5.6.3", + "vite": "^5.4.10", "vite-tsconfig-paths": "^4.3.2" }, "msw": { diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 059b1de..d3fc618 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@ant-design/icons': - specifier: ^5.3.7 - version: 5.5.1(react-dom@18.3.1)(react@18.3.1) + specifier: ^5.5.1 + version: 5.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@xterm/addon-attach': specifier: ^0.11.0 version: 0.11.0(@xterm/xterm@5.5.0) @@ -21,10 +21,10 @@ importers: specifier: ^5.5.0 version: 5.5.0 antd: - specifier: ^5.19.2 - version: 5.21.6(react-dom@18.3.1)(react@18.3.1) + specifier: ^5.21.6 + version: 5.21.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) axios: - specifier: ^1.7.4 + specifier: ^1.7.7 version: 1.7.7 clsx: specifier: ^2.1.1 @@ -33,17 +33,17 @@ importers: specifier: ^4.2.0 version: 4.2.0 i18next: - specifier: ^23.11.5 + specifier: ^23.16.4 version: 23.16.4 jotai: - specifier: ^2.9.0 + specifier: ^2.10.1 version: 2.10.1(@types/react@18.3.12)(react@18.3.1) js-cookie: specifier: ^3.0.5 version: 3.0.5 lucide-react: - specifier: ^0.408.0 - version: 0.408.0(react@18.3.1) + specifier: ^0.469.0 + version: 0.469.0(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -51,36 +51,36 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) react-error-boundary: - specifier: ^4.0.13 + specifier: ^4.1.2 version: 4.1.2(react@18.3.1) react-helmet-async: specifier: ^2.0.5 version: 2.0.5(react@18.3.1) react-i18next: - specifier: ^14.1.2 - version: 14.1.3(i18next@23.16.4)(react-dom@18.3.1)(react@18.3.1) + specifier: ^14.1.3 + version: 14.1.3(i18next@23.16.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-responsive: specifier: ^10.0.0 version: 10.0.0(react@18.3.1) react-router-dom: - specifier: ^6.23.1 - version: 6.27.0(react-dom@18.3.1)(react@18.3.1) + specifier: ^6.27.0 + version: 6.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-simple-keyboard: - specifier: ^3.7.124 - version: 3.8.19(react-dom@18.3.1)(react@18.3.1) + specifier: ^3.8.19 + version: 3.8.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) semver: specifier: ^7.6.3 version: 7.6.3 vaul: - specifier: ^0.9.1 - version: 0.9.9(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + specifier: ^0.9.9 + version: 0.9.9(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) websocket: specifier: ^1.0.35 version: 1.0.35 devDependencies: '@ianvs/prettier-plugin-sort-imports': - specifier: ^4.2.1 - version: 4.3.1(prettier@3.3.3) + specifier: ^4.3.1 + version: 4.3.1(prettier@3.4.2) '@types/crypto-js': specifier: ^4.2.2 version: 4.2.2 @@ -88,10 +88,10 @@ importers: specifier: ^3.0.6 version: 3.0.6 '@types/react': - specifier: ^18.3.2 + specifier: ^18.3.12 version: 18.3.12 '@types/react-dom': - specifier: ^18.3.0 + specifier: ^18.3.1 version: 18.3.1 '@types/semver': specifier: ^7.5.8 @@ -100,56 +100,56 @@ importers: specifier: ^1.0.10 version: 1.0.10 '@typescript-eslint/eslint-plugin': - specifier: ^7.10.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0)(eslint@8.57.1)(typescript@5.6.3) + specifier: ^8.19.1 + version: 8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3) '@typescript-eslint/parser': - specifier: ^7.10.0 - version: 7.18.0(eslint@8.57.1)(typescript@5.6.3) + specifier: ^8.19.1 + version: 8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3) '@vitejs/plugin-react': - specifier: ^4.2.1 - version: 4.3.3(vite@5.4.10) + specifier: ^4.3.3 + version: 4.3.3(vite@5.4.10(@types/node@22.9.0)) autoprefixer: - specifier: ^10.4.19 + specifier: ^10.4.20 version: 10.4.20(postcss@8.4.47) eslint: - specifier: ^8.57.0 - version: 8.57.1 + specifier: ^9.17.0 + version: 9.17.0(jiti@1.21.6) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@8.57.1) + version: 9.1.0(eslint@9.17.0(jiti@1.21.6)) eslint-plugin-react: - specifier: ^7.34.1 - version: 7.37.2(eslint@8.57.1) + specifier: ^7.37.2 + version: 7.37.2(eslint@9.17.0(jiti@1.21.6)) eslint-plugin-react-hooks: - specifier: ^4.6.2 - version: 4.6.2(eslint@8.57.1) + specifier: ^5.1.0 + version: 5.1.0(eslint@9.17.0(jiti@1.21.6)) eslint-plugin-react-refresh: - specifier: ^0.4.7 - version: 0.4.14(eslint@8.57.1) + specifier: ^0.4.16 + version: 0.4.16(eslint@9.17.0(jiti@1.21.6)) msw: specifier: ^2.6.0 version: 2.6.0(@types/node@22.9.0)(typescript@5.6.3) postcss: - specifier: ^8.4.38 + specifier: ^8.4.47 version: 8.4.47 prettier: - specifier: ^3.2.5 - version: 3.3.3 + specifier: ^3.4.2 + version: 3.4.2 prettier-plugin-tailwindcss: - specifier: ^0.5.14 - version: 0.5.14(@ianvs/prettier-plugin-sort-imports@4.3.1)(prettier@3.3.3) + specifier: ^0.6.9 + version: 0.6.9(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.4.2))(prettier@3.4.2) tailwindcss: - specifier: ^3.4.13 + specifier: ^3.4.14 version: 3.4.14 typescript: - specifier: ^5.4.5 + specifier: ^5.6.3 version: 5.6.3 vite: - specifier: ^5.4.8 + specifier: ^5.4.10 version: 5.4.10(@types/node@22.9.0) vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.6.3)(vite@5.4.10) + version: 4.3.2(typescript@5.6.3)(vite@5.4.10(@types/node@22.9.0)) packages: @@ -445,26 +445,49 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/config-array@0.19.1': + resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/core@0.9.1': + resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead + '@eslint/eslintrc@3.2.0': + resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.17.0': + resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.5': + resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.4': + resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.1': + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} '@ianvs/prettier-plugin-sort-imports@4.3.1': resolution: {integrity: sha512-ZHwbyjkANZOjaBm3ZosADD2OUYGFzQGxfy67HmGZU94mHqe7g1LCMA7YYKB1Cq+UTPCBqlAYapY0KXAjKEw8Sg==} @@ -881,6 +904,9 @@ packages: '@types/js-cookie@3.0.6': resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@22.9.0': resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} @@ -905,66 +931,52 @@ packages: '@types/websocket@1.0.10': resolution: {integrity: sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==} - '@typescript-eslint/eslint-plugin@7.18.0': - resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/eslint-plugin@8.19.1': + resolution: {integrity: sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/parser@7.18.0': - resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/parser@8.19.1': + resolution: {integrity: sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/scope-manager@7.18.0': - resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.19.1': + resolution: {integrity: sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@7.18.0': - resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/type-utils@8.19.1': + resolution: {integrity: sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/types@7.18.0': - resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.19.1': + resolution: {integrity: sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@7.18.0': - resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/typescript-estree@8.19.1': + resolution: {integrity: sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/utils@7.18.0': - resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/utils@8.19.1': + resolution: {integrity: sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/visitor-keys@7.18.0': - resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@typescript-eslint/visitor-keys@8.19.1': + resolution: {integrity: sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-react@4.3.3': resolution: {integrity: sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==} @@ -1052,10 +1064,6 @@ packages: array-tree-filter@2.1.0: resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - array.prototype.findlast@1.2.5: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} @@ -1188,8 +1196,8 @@ packages: copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} crypto-js@4.2.0: @@ -1263,10 +1271,6 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -1274,10 +1278,6 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1351,16 +1351,16 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-plugin-react-hooks@4.6.2: - resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} + eslint-plugin-react-hooks@5.1.0: + resolution: {integrity: sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==} engines: {node: '>=10'} peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - eslint-plugin-react-refresh@0.4.14: - resolution: {integrity: sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==} + eslint-plugin-react-refresh@0.4.16: + resolution: {integrity: sha512-slterMlxAhov/DZO8NScf6mEeMBBXodFUolijDvrtTxyezyLoTQaa73FyYus/VbTdftd8wBgBxPMRk3poleXNQ==} peerDependencies: - eslint: '>=7' + eslint: '>=8.40' eslint-plugin-react@7.37.2: resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==} @@ -1368,27 +1368,35 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.17.0: + resolution: {integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true esniff@2.0.1: resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} engines: {node: '>=0.10'} - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} @@ -1428,9 +1436,9 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} @@ -1440,9 +1448,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} @@ -1470,9 +1478,6 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1520,26 +1525,18 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -1603,13 +1600,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -1690,10 +1680,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -1833,8 +1819,8 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@0.408.0: - resolution: {integrity: sha512-8kETAAeWmOvtGIr7HPHm51DXoxlfkNncQ5FZWXR+abX8saQwMYXANWIkUstaYtcKSo/imOe/q+tVFA8ANzdSVA==} + lucide-react@0.469.0: + resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1891,8 +1877,8 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -1949,9 +1935,6 @@ packages: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1978,10 +1961,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1996,10 +1975,6 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2064,8 +2039,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-plugin-tailwindcss@0.5.14: - resolution: {integrity: sha512-Puaz+wPUAhFp8Lo9HuciYKM2Y2XExESjeT+9NQoVFXZsPPnc9VYss2SpxdQ6vbatmt8/4+SN0oe0I1cPDABg9Q==} + prettier-plugin-tailwindcss@0.6.9: + resolution: {integrity: sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==} engines: {node: '>=14.21.3'} peerDependencies: '@ianvs/prettier-plugin-sort-imports': '*' @@ -2079,6 +2054,7 @@ packages: prettier-plugin-import-sort: '*' prettier-plugin-jsdoc: '*' prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' prettier-plugin-organize-attributes: '*' prettier-plugin-organize-imports: '*' prettier-plugin-sort-imports: '*' @@ -2105,6 +2081,8 @@ packages: optional: true prettier-plugin-marko: optional: true + prettier-plugin-multiline-arrays: + optional: true prettier-plugin-organize-attributes: optional: true prettier-plugin-organize-imports: @@ -2116,8 +2094,8 @@ packages: prettier-plugin-svelte: optional: true - prettier@3.3.3: - resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + prettier@3.4.2: + resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} engines: {node: '>=14'} hasBin: true @@ -2512,11 +2490,6 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - rollup@4.24.4: resolution: {integrity: sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2578,10 +2551,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2655,9 +2624,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -2680,11 +2646,11 @@ packages: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} - ts-api-utils@1.4.0: - resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} - engines: {node: '>=16'} + ts-api-utils@2.0.0: + resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} + engines: {node: '>=18.12'} peerDependencies: - typescript: '>=4.2.0' + typescript: '>=4.8.4' ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -2706,10 +2672,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} @@ -2883,9 +2845,6 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2931,22 +2890,22 @@ snapshots: dependencies: '@ctrl/tinycolor': 3.6.1 - '@ant-design/cssinjs-utils@1.1.1(react-dom@18.3.1)(react@18.3.1)': + '@ant-design/cssinjs-utils@1.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@ant-design/cssinjs': 1.21.1(react-dom@18.3.1)(react@18.3.1) + '@ant-design/cssinjs': 1.21.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@babel/runtime': 7.26.0 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@ant-design/cssinjs@1.21.1(react-dom@18.3.1)(react@18.3.1)': + '@ant-design/cssinjs@1.21.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 '@emotion/hash': 0.8.0 '@emotion/unitless': 0.7.5 classnames: 2.5.1 csstype: 3.1.3 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) stylis: 4.3.4 @@ -2957,13 +2916,13 @@ snapshots: '@ant-design/icons-svg@4.4.2': {} - '@ant-design/icons@5.5.1(react-dom@18.3.1)(react@18.3.1)': + '@ant-design/icons@5.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@ant-design/colors': 7.1.0 '@ant-design/icons-svg': 4.4.2 '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -3178,19 +3137,31 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(jiti@1.21.6))': dependencies: - eslint: 8.57.1 + eslint: 9.17.0(jiti@1.21.6) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/eslintrc@2.1.4': + '@eslint/config-array@0.19.1': + dependencies: + '@eslint/object-schema': 2.1.5 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.9.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.2.0': dependencies: ajv: 6.12.6 debug: 4.3.7 - espree: 9.6.1 - globals: 13.24.0 + espree: 10.3.0 + globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -3199,28 +3170,35 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@8.57.1': {} + '@eslint/js@9.17.0': {} - '@humanwhocodes/config-array@0.13.0': + '@eslint/object-schema@2.1.5': {} + + '@eslint/plugin-kit@0.2.4': dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/object-schema@2.0.3': {} + '@humanwhocodes/retry@0.3.1': {} - '@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3)': + '@humanwhocodes/retry@0.4.1': {} + + '@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.4.2)': dependencies: '@babel/core': 7.26.0 '@babel/generator': 7.26.2 '@babel/parser': 7.26.2 '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 - prettier: 3.3.3 + prettier: 3.4.2 semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -3314,139 +3292,154 @@ snapshots: '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-context@1.1.1(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 - '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 aria-hidden: 1.2.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-remove-scroll: 2.6.0(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 - '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 - '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 '@radix-ui/react-id@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 react: 18.3.1 - - '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 + + '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 - '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 - '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1)': + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 - '@types/react-dom': 18.3.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 '@radix-ui/react-slot@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: - '@types/react': 18.3.12 react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 '@rc-component/async-validator@5.0.4': dependencies: '@babel/runtime': 7.26.0 - '@rc-component/color-picker@2.0.1(react-dom@18.3.1)(react@18.3.1)': + '@rc-component/color-picker@2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@ant-design/fast-color': 2.0.6 '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@rc-component/context@1.4.0(react-dom@18.3.1)(react@18.3.1)': + '@rc-component/context@1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -3454,48 +3447,48 @@ snapshots: dependencies: '@babel/runtime': 7.26.0 - '@rc-component/mutate-observer@1.1.0(react-dom@18.3.1)(react@18.3.1)': + '@rc-component/mutate-observer@1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@rc-component/portal@1.1.2(react-dom@18.3.1)(react@18.3.1)': + '@rc-component/portal@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@rc-component/qrcode@1.0.0(react-dom@18.3.1)(react@18.3.1)': + '@rc-component/qrcode@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@rc-component/tour@1.15.1(react-dom@18.3.1)(react@18.3.1)': + '@rc-component/tour@1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 - '@rc-component/portal': 1.1.2(react-dom@18.3.1)(react@18.3.1) - '@rc-component/trigger': 2.2.3(react-dom@18.3.1)(react@18.3.1) + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@rc-component/trigger@2.2.3(react-dom@18.3.1)(react@18.3.1)': + '@rc-component/trigger@2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 - '@rc-component/portal': 1.1.2(react-dom@18.3.1)(react@18.3.1) + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -3584,6 +3577,8 @@ snapshots: '@types/js-cookie@3.0.6': {} + '@types/json-schema@7.0.15': {} + '@types/node@22.9.0': dependencies: undici-types: 6.19.8 @@ -3609,86 +3604,84 @@ snapshots: dependencies: '@types/node': 22.9.0 - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0)(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 8.57.1 + '@typescript-eslint/parser': 8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/type-utils': 8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.19.1 + eslint: 9.17.0(jiti@1.21.6) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.4.0(typescript@5.6.3) + ts-api-utils: 2.0.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.19.1 debug: 4.3.7 - eslint: 8.57.1 + eslint: 9.17.0(jiti@1.21.6) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@7.18.0': + '@typescript-eslint/scope-manager@8.19.1': dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/visitor-keys': 8.19.1 - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/type-utils@8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.6.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3) debug: 4.3.7 - eslint: 8.57.1 - ts-api-utils: 1.4.0(typescript@5.6.3) + eslint: 9.17.0(jiti@1.21.6) + ts-api-utils: 2.0.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@7.18.0': {} + '@typescript-eslint/types@8.19.1': {} - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.3)': + '@typescript-eslint/typescript-estree@8.19.1(typescript@5.6.3)': dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/visitor-keys': 8.19.1 debug: 4.3.7 - globby: 11.1.0 + fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.6.3) + ts-api-utils: 2.0.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/utils@8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) - eslint: 8.57.1 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@1.21.6)) + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.6.3) + eslint: 9.17.0(jiti@1.21.6) + typescript: 5.6.3 transitivePeerDependencies: - supports-color - - typescript - '@typescript-eslint/visitor-keys@7.18.0': + '@typescript-eslint/visitor-keys@8.19.1': dependencies: - '@typescript-eslint/types': 7.18.0 - eslint-visitor-keys: 3.4.3 + '@typescript-eslint/types': 8.19.1 + eslint-visitor-keys: 4.2.0 - '@ungap/structured-clone@1.2.0': {} - - '@vitejs/plugin-react@4.3.3(vite@5.4.10)': + '@vitejs/plugin-react@4.3.3(vite@5.4.10(@types/node@22.9.0))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) @@ -3736,55 +3729,55 @@ snapshots: ansi-styles@6.2.1: {} - antd@5.21.6(react-dom@18.3.1)(react@18.3.1): + antd@5.21.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@ant-design/colors': 7.1.0 - '@ant-design/cssinjs': 1.21.1(react-dom@18.3.1)(react@18.3.1) - '@ant-design/cssinjs-utils': 1.1.1(react-dom@18.3.1)(react@18.3.1) - '@ant-design/icons': 5.5.1(react-dom@18.3.1)(react@18.3.1) + '@ant-design/cssinjs': 1.21.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/cssinjs-utils': 1.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/icons': 5.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@ant-design/react-slick': 1.1.2(react@18.3.1) '@babel/runtime': 7.26.0 '@ctrl/tinycolor': 3.6.1 - '@rc-component/color-picker': 2.0.1(react-dom@18.3.1)(react@18.3.1) - '@rc-component/mutate-observer': 1.1.0(react-dom@18.3.1)(react@18.3.1) - '@rc-component/qrcode': 1.0.0(react-dom@18.3.1)(react@18.3.1) - '@rc-component/tour': 1.15.1(react-dom@18.3.1)(react@18.3.1) - '@rc-component/trigger': 2.2.3(react-dom@18.3.1)(react@18.3.1) + '@rc-component/color-picker': 2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/mutate-observer': 1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/qrcode': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/tour': 1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 copy-to-clipboard: 3.3.3 dayjs: 1.11.13 - rc-cascader: 3.28.2(react-dom@18.3.1)(react@18.3.1) - rc-checkbox: 3.3.0(react-dom@18.3.1)(react@18.3.1) - rc-collapse: 3.8.0(react-dom@18.3.1)(react@18.3.1) - rc-dialog: 9.6.0(react-dom@18.3.1)(react@18.3.1) - rc-drawer: 7.2.0(react-dom@18.3.1)(react@18.3.1) - rc-dropdown: 4.2.0(react-dom@18.3.1)(react@18.3.1) - rc-field-form: 2.4.0(react-dom@18.3.1)(react@18.3.1) - rc-image: 7.11.0(react-dom@18.3.1)(react@18.3.1) - rc-input: 1.6.3(react-dom@18.3.1)(react@18.3.1) - rc-input-number: 9.2.0(react-dom@18.3.1)(react@18.3.1) - rc-mentions: 2.16.1(react-dom@18.3.1)(react@18.3.1) - rc-menu: 9.15.1(react-dom@18.3.1)(react@18.3.1) - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-notification: 5.6.2(react-dom@18.3.1)(react@18.3.1) - rc-pagination: 4.3.0(react-dom@18.3.1)(react@18.3.1) - rc-picker: 4.6.15(dayjs@1.11.13)(react-dom@18.3.1)(react@18.3.1) - rc-progress: 4.0.0(react-dom@18.3.1)(react@18.3.1) - rc-rate: 2.13.0(react-dom@18.3.1)(react@18.3.1) - rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1) - rc-segmented: 2.5.0(react-dom@18.3.1)(react@18.3.1) - rc-select: 14.15.2(react-dom@18.3.1)(react@18.3.1) - rc-slider: 11.1.7(react-dom@18.3.1)(react@18.3.1) - rc-steps: 6.0.1(react-dom@18.3.1)(react@18.3.1) - rc-switch: 4.1.0(react-dom@18.3.1)(react@18.3.1) - rc-table: 7.47.5(react-dom@18.3.1)(react@18.3.1) - rc-tabs: 15.3.0(react-dom@18.3.1)(react@18.3.1) - rc-textarea: 1.8.2(react-dom@18.3.1)(react@18.3.1) - rc-tooltip: 6.2.1(react-dom@18.3.1)(react@18.3.1) - rc-tree: 5.9.0(react-dom@18.3.1)(react@18.3.1) - rc-tree-select: 5.23.0(react-dom@18.3.1)(react@18.3.1) - rc-upload: 4.8.1(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-cascader: 3.28.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-checkbox: 3.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-collapse: 3.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-drawer: 7.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-dropdown: 4.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-field-form: 2.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-image: 7.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-input: 1.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-input-number: 9.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-mentions: 2.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-notification: 5.6.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-pagination: 4.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-picker: 4.6.15(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-progress: 4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-rate: 2.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-segmented: 2.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-select: 14.15.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-slider: 11.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-steps: 6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-switch: 4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-table: 7.47.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tabs: 15.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-textarea: 1.8.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tooltip: 6.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree-select: 5.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-upload: 4.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 @@ -3825,8 +3818,6 @@ snapshots: array-tree-filter@2.1.0: {} - array-union@2.1.0: {} - array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.7 @@ -3988,7 +3979,7 @@ snapshots: dependencies: toggle-selection: 1.0.6 - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -4055,20 +4046,12 @@ snapshots: didyoumean@1.2.2: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - dlv@1.1.3: {} doctrine@2.1.0: dependencies: esutils: 2.0.3 - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - eastasianwidth@0.2.0: {} electron-to-chromium@1.5.51: {} @@ -4218,19 +4201,19 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@9.1.0(eslint@8.57.1): + eslint-config-prettier@9.1.0(eslint@9.17.0(jiti@1.21.6)): dependencies: - eslint: 8.57.1 + eslint: 9.17.0(jiti@1.21.6) - eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): + eslint-plugin-react-hooks@5.1.0(eslint@9.17.0(jiti@1.21.6)): dependencies: - eslint: 8.57.1 + eslint: 9.17.0(jiti@1.21.6) - eslint-plugin-react-refresh@0.4.14(eslint@8.57.1): + eslint-plugin-react-refresh@0.4.16(eslint@9.17.0(jiti@1.21.6)): dependencies: - eslint: 8.57.1 + eslint: 9.17.0(jiti@1.21.6) - eslint-plugin-react@7.37.2(eslint@8.57.1): + eslint-plugin-react@7.37.2(eslint@9.17.0(jiti@1.21.6)): dependencies: array-includes: 3.1.8 array.prototype.findlast: 1.2.5 @@ -4238,7 +4221,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.0 - eslint: 8.57.1 + eslint: 9.17.0(jiti@1.21.6) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -4252,53 +4235,53 @@ snapshots: string.prototype.matchall: 4.0.11 string.prototype.repeat: 1.0.0 - eslint-scope@7.2.2: + eslint-scope@8.2.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint@8.57.1: + eslint-visitor-keys@4.2.0: {} + + eslint@9.17.0(jiti@1.21.6): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@1.21.6)) '@eslint-community/regexpp': 4.12.1 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 + '@eslint/config-array': 0.19.1 + '@eslint/core': 0.9.1 + '@eslint/eslintrc': 3.2.0 + '@eslint/js': 9.17.0 + '@eslint/plugin-kit': 0.2.4 + '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 + '@humanwhocodes/retry': 0.4.1 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 debug: 4.3.7 - doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 + file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 + optionalDependencies: + jiti: 1.21.6 transitivePeerDependencies: - supports-color @@ -4309,11 +4292,11 @@ snapshots: event-emitter: 0.3.5 type: 2.7.3 - espree@9.6.1: + espree@10.3.0: dependencies: acorn: 8.14.0 acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 3.4.3 + eslint-visitor-keys: 4.2.0 esquery@1.6.0: dependencies: @@ -4354,9 +4337,9 @@ snapshots: dependencies: reusify: 1.0.4 - file-entry-cache@6.0.1: + file-entry-cache@8.0.0: dependencies: - flat-cache: 3.2.0 + flat-cache: 4.0.1 fill-range@7.1.1: dependencies: @@ -4367,11 +4350,10 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flat-cache@3.2.0: + flat-cache@4.0.1: dependencies: flatted: 3.3.1 keyv: 4.5.4 - rimraf: 3.0.2 flatted@3.3.1: {} @@ -4383,7 +4365,7 @@ snapshots: foreground-child@3.3.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 form-data@4.0.1: @@ -4394,8 +4376,6 @@ snapshots: fraction.js@4.3.7: {} - fs.realpath@1.0.0: {} - fsevents@2.3.3: optional: true @@ -4447,35 +4427,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - globals@11.12.0: {} - globals@13.24.0: - dependencies: - type-fest: 0.20.2 + globals@14.0.0: {} globalthis@1.0.4: dependencies: define-properties: 1.2.1 gopd: 1.0.1 - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - globrex@0.1.2: {} gopd@1.0.1: @@ -4527,13 +4487,6 @@ snapshots: imurmurhash@0.1.4: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - internal-slot@1.0.7: dependencies: es-errors: 1.3.0 @@ -4608,8 +4561,6 @@ snapshots: is-number@7.0.0: {} - is-path-inside@3.0.3: {} - is-regex@1.1.4: dependencies: call-bind: 1.0.7 @@ -4667,7 +4618,7 @@ snapshots: jiti@1.21.6: {} jotai@2.10.1(@types/react@18.3.12)(react@18.3.1): - dependencies: + optionalDependencies: '@types/react': 18.3.12 react: 18.3.1 @@ -4731,7 +4682,7 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@0.408.0(react@18.3.1): + lucide-react@0.469.0(react@18.3.1): dependencies: react: 18.3.1 @@ -4785,8 +4736,9 @@ snapshots: path-to-regexp: 6.3.0 strict-event-emitter: 0.5.1 type-fest: 4.26.1 - typescript: 5.6.3 yargs: 17.7.2 + optionalDependencies: + typescript: 5.6.3 transitivePeerDependencies: - '@types/node' @@ -4798,7 +4750,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.7: {} + nanoid@3.3.8: {} natural-compare@1.4.0: {} @@ -4846,10 +4798,6 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 - once@1.4.0: - dependencies: - wrappy: 1.0.2 - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4877,8 +4825,6 @@ snapshots: path-exists@4.0.0: {} - path-is-absolute@1.0.1: {} - path-key@3.1.1: {} path-parse@1.0.7: {} @@ -4890,8 +4836,6 @@ snapshots: path-to-regexp@6.3.0: {} - path-type@4.0.0: {} - picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -4917,8 +4861,9 @@ snapshots: postcss-load-config@4.0.2(postcss@8.4.47): dependencies: lilconfig: 3.1.2 - postcss: 8.4.47 yaml: 2.6.0 + optionalDependencies: + postcss: 8.4.47 postcss-nested@6.2.0(postcss@8.4.47): dependencies: @@ -4934,18 +4879,19 @@ snapshots: postcss@8.4.47: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 prelude-ls@1.2.1: {} - prettier-plugin-tailwindcss@0.5.14(@ianvs/prettier-plugin-sort-imports@4.3.1)(prettier@3.3.3): + prettier-plugin-tailwindcss@0.6.9(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.4.2))(prettier@3.4.2): dependencies: - '@ianvs/prettier-plugin-sort-imports': 4.3.1(prettier@3.3.3) - prettier: 3.3.3 + prettier: 3.4.2 + optionalDependencies: + '@ianvs/prettier-plugin-sort-imports': 4.3.1(prettier@3.4.2) - prettier@3.3.3: {} + prettier@3.4.2: {} prop-types@15.8.1: dependencies: @@ -4963,321 +4909,322 @@ snapshots: queue-microtask@1.2.3: {} - rc-cascader@3.28.2(react-dom@18.3.1)(react@18.3.1): + rc-cascader@3.28.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 array-tree-filter: 2.1.0 classnames: 2.5.1 - rc-select: 14.15.2(react-dom@18.3.1)(react@18.3.1) - rc-tree: 5.9.0(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-select: 14.15.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-checkbox@3.3.0(react-dom@18.3.1)(react@18.3.1): + rc-checkbox@3.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-collapse@3.8.0(react-dom@18.3.1)(react@18.3.1): + rc-collapse@3.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-dialog@9.6.0(react-dom@18.3.1)(react@18.3.1): + rc-dialog@9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 - '@rc-component/portal': 1.1.2(react-dom@18.3.1)(react@18.3.1) + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-drawer@7.2.0(react-dom@18.3.1)(react@18.3.1): + rc-drawer@7.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 - '@rc-component/portal': 1.1.2(react-dom@18.3.1)(react@18.3.1) + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-dropdown@4.2.0(react-dom@18.3.1)(react@18.3.1): + rc-dropdown@4.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 - '@rc-component/trigger': 2.2.3(react-dom@18.3.1)(react@18.3.1) + '@rc-component/trigger': 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-field-form@2.4.0(react-dom@18.3.1)(react@18.3.1): + rc-field-form@2.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 '@rc-component/async-validator': 5.0.4 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-image@7.11.0(react-dom@18.3.1)(react@18.3.1): + rc-image@7.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 - '@rc-component/portal': 1.1.2(react-dom@18.3.1)(react@18.3.1) + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 - rc-dialog: 9.6.0(react-dom@18.3.1)(react@18.3.1) - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-input-number@9.2.0(react-dom@18.3.1)(react@18.3.1): + rc-input-number@9.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 '@rc-component/mini-decimal': 1.1.0 classnames: 2.5.1 - rc-input: 1.6.3(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-input: 1.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-input@1.6.3(react-dom@18.3.1)(react@18.3.1): + rc-input@1.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-mentions@2.16.1(react-dom@18.3.1)(react@18.3.1): + rc-mentions@2.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 - '@rc-component/trigger': 2.2.3(react-dom@18.3.1)(react@18.3.1) + '@rc-component/trigger': 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 - rc-input: 1.6.3(react-dom@18.3.1)(react@18.3.1) - rc-menu: 9.15.1(react-dom@18.3.1)(react@18.3.1) - rc-textarea: 1.8.2(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-input: 1.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-textarea: 1.8.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-menu@9.15.1(react-dom@18.3.1)(react@18.3.1): + rc-menu@9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 - '@rc-component/trigger': 2.2.3(react-dom@18.3.1)(react@18.3.1) + '@rc-component/trigger': 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-overflow: 1.3.2(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-motion@2.9.3(react-dom@18.3.1)(react@18.3.1): + rc-motion@2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-notification@5.6.2(react-dom@18.3.1)(react@18.3.1): + rc-notification@5.6.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-overflow@1.3.2(react-dom@18.3.1)(react@18.3.1): + rc-overflow@1.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-resize-observer: 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-pagination@4.3.0(react-dom@18.3.1)(react@18.3.1): + rc-pagination@4.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-picker@4.6.15(dayjs@1.11.13)(react-dom@18.3.1)(react@18.3.1): + rc-picker@4.6.15(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 - '@rc-component/trigger': 2.2.3(react-dom@18.3.1)(react@18.3.1) + '@rc-component/trigger': 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 + rc-overflow: 1.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: dayjs: 1.11.13 - rc-overflow: 1.3.2(react-dom@18.3.1)(react@18.3.1) - rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - rc-progress@4.0.0(react-dom@18.3.1)(react@18.3.1): + rc-progress@4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-rate@2.13.0(react-dom@18.3.1)(react@18.3.1): + rc-rate@2.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-resize-observer@1.4.0(react-dom@18.3.1)(react@18.3.1): + rc-resize-observer@1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) resize-observer-polyfill: 1.5.1 - rc-segmented@2.5.0(react-dom@18.3.1)(react@18.3.1): + rc-segmented@2.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-select@14.15.2(react-dom@18.3.1)(react@18.3.1): + rc-select@14.15.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 - '@rc-component/trigger': 2.2.3(react-dom@18.3.1)(react@18.3.1) + '@rc-component/trigger': 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-overflow: 1.3.2(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) - rc-virtual-list: 3.15.0(react-dom@18.3.1)(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-slider@11.1.7(react-dom@18.3.1)(react@18.3.1): + rc-slider@11.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-steps@6.0.1(react-dom@18.3.1)(react@18.3.1): + rc-steps@6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-switch@4.1.0(react-dom@18.3.1)(react@18.3.1): + rc-switch@4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-table@7.47.5(react-dom@18.3.1)(react@18.3.1): + rc-table@7.47.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 - '@rc-component/context': 1.4.0(react-dom@18.3.1)(react@18.3.1) + '@rc-component/context': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 - rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) - rc-virtual-list: 3.15.0(react-dom@18.3.1)(react@18.3.1) + rc-resize-observer: 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-tabs@15.3.0(react-dom@18.3.1)(react@18.3.1): + rc-tabs@15.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-dropdown: 4.2.0(react-dom@18.3.1)(react@18.3.1) - rc-menu: 9.15.1(react-dom@18.3.1)(react@18.3.1) - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-dropdown: 4.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-textarea@1.8.2(react-dom@18.3.1)(react@18.3.1): + rc-textarea@1.8.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-input: 1.6.3(react-dom@18.3.1)(react@18.3.1) - rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-input: 1.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-tooltip@6.2.1(react-dom@18.3.1)(react@18.3.1): + rc-tooltip@6.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 - '@rc-component/trigger': 2.2.3(react-dom@18.3.1)(react@18.3.1) + '@rc-component/trigger': 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-tree-select@5.23.0(react-dom@18.3.1)(react@18.3.1): + rc-tree-select@5.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-select: 14.15.2(react-dom@18.3.1)(react@18.3.1) - rc-tree: 5.9.0(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-select: 14.15.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-tree@5.9.0(react-dom@18.3.1)(react@18.3.1): + rc-tree@5.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-motion: 2.9.3(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) - rc-virtual-list: 3.15.0(react-dom@18.3.1)(react@18.3.1) + rc-motion: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-upload@4.8.1(react-dom@18.3.1)(react@18.3.1): + rc-upload@4.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-util@5.43.0(react-dom@18.3.1)(react@18.3.1): + rc-util@5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-is: 18.3.1 - rc-virtual-list@3.15.0(react-dom@18.3.1)(react@18.3.1): + rc-virtual-list@3.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 classnames: 2.5.1 - rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1) - rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) + rc-resize-observer: 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -5301,12 +5248,13 @@ snapshots: react-fast-compare: 3.2.2 shallowequal: 1.1.0 - react-i18next@14.1.3(i18next@23.16.4)(react-dom@18.3.1)(react@18.3.1): + react-i18next@14.1.3(i18next@23.16.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 html-parse-stringify: 3.0.1 i18next: 23.16.4 react: 18.3.1 + optionalDependencies: react-dom: 18.3.1(react@18.3.1) react-is@16.13.1: {} @@ -5317,20 +5265,22 @@ snapshots: react-remove-scroll-bar@2.3.6(@types/react@18.3.12)(react@18.3.1): dependencies: - '@types/react': 18.3.12 react: 18.3.1 react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 react-remove-scroll@2.6.0(@types/react@18.3.12)(react@18.3.1): dependencies: - '@types/react': 18.3.12 react: 18.3.1 react-remove-scroll-bar: 2.3.6(@types/react@18.3.12)(react@18.3.1) react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) tslib: 2.8.1 use-callback-ref: 1.3.2(@types/react@18.3.12)(react@18.3.1) use-sidecar: 1.1.2(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 react-responsive@10.0.0(react@18.3.1): dependencies: @@ -5340,7 +5290,7 @@ snapshots: react: 18.3.1 shallow-equal: 3.1.0 - react-router-dom@6.27.0(react-dom@18.3.1)(react@18.3.1): + react-router-dom@6.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@remix-run/router': 1.20.0 react: 18.3.1 @@ -5352,18 +5302,19 @@ snapshots: '@remix-run/router': 1.20.0 react: 18.3.1 - react-simple-keyboard@3.8.19(react-dom@18.3.1)(react@18.3.1): + react-simple-keyboard@3.8.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-style-singleton@2.2.1(@types/react@18.3.12)(react@18.3.1): dependencies: - '@types/react': 18.3.12 get-nonce: 1.0.1 invariant: 2.2.4 react: 18.3.1 tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 react@18.3.1: dependencies: @@ -5418,10 +5369,6 @@ snapshots: reusify@1.0.4: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - rollup@4.24.4: dependencies: '@types/estree': 1.0.6 @@ -5510,8 +5457,6 @@ snapshots: signal-exit@4.1.0: {} - slash@3.0.0: {} - source-map-js@1.2.1: {} statuses@2.0.1: {} @@ -5626,8 +5571,6 @@ snapshots: transitivePeerDependencies: - ts-node - text-table@0.2.0: {} - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -5651,14 +5594,14 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 - ts-api-utils@1.4.0(typescript@5.6.3): + ts-api-utils@2.0.0(typescript@5.6.3): dependencies: typescript: 5.6.3 ts-interface-checker@0.1.13: {} tsconfck@3.1.4(typescript@5.6.3): - dependencies: + optionalDependencies: typescript: 5.6.3 tslib@2.8.1: {} @@ -5667,8 +5610,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@0.20.2: {} - type-fest@0.21.3: {} type-fest@4.26.1: {} @@ -5741,16 +5682,18 @@ snapshots: use-callback-ref@1.3.2(@types/react@18.3.12)(react@18.3.1): dependencies: - '@types/react': 18.3.12 react: 18.3.1 tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 use-sidecar@1.1.2(@types/react@18.3.12)(react@18.3.1): dependencies: - '@types/react': 18.3.12 detect-node-es: 1.1.0 react: 18.3.1 tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.12 utf-8-validate@5.0.10: dependencies: @@ -5758,20 +5701,21 @@ snapshots: util-deprecate@1.0.2: {} - vaul@0.9.9(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1): + vaul@0.9.9(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: - '@types/react' - '@types/react-dom' - vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.4.10): + vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.4.10(@types/node@22.9.0)): dependencies: debug: 4.3.7 globrex: 0.1.2 tsconfck: 3.1.4(typescript@5.6.3) + optionalDependencies: vite: 5.4.10(@types/node@22.9.0) transitivePeerDependencies: - supports-color @@ -5779,11 +5723,11 @@ snapshots: vite@5.4.10(@types/node@22.9.0): dependencies: - '@types/node': 22.9.0 esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.24.4 optionalDependencies: + '@types/node': 22.9.0 fsevents: 2.3.3 void-elements@3.1.0: {} @@ -5861,8 +5805,6 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 - wrappy@1.0.2: {} - y18n@5.0.8: {} yaeti@0.0.6: {} diff --git a/web/src/api/auth.ts b/web/src/api/auth.ts index b0320f0..0741cb6 100644 --- a/web/src/api/auth.ts +++ b/web/src/api/auth.ts @@ -8,6 +8,10 @@ export function login(username: string, password: string) { return http.post('/api/auth/login', data); } +export function getAccount() { + return http.get('/api/auth/account'); +} + export function changePassword(username: string, password: string) { const data = { username, @@ -19,11 +23,3 @@ export function changePassword(username: string, password: string) { export function isPasswordUpdated() { return http.get('/api/auth/password'); } - -export function connectWifi(ssid: string, password: string) { - const data = { - ssid, - password - }; - return http.post('/api/auth/wifi', data); -} diff --git a/web/src/api/network.ts b/web/src/api/network.ts index e1b410e..9a094fc 100644 --- a/web/src/api/network.ts +++ b/web/src/api/network.ts @@ -26,14 +26,24 @@ export function installTailscale() { return http.post('/api/network/tailscale/install'); } +// uninstall tailscale +export function uninstallTailscale() { + return http.post('/api/network/tailscale/uninstall'); +} + // get tailscale status export function getTailscaleStatus() { return http.get('/api/network/tailscale/status'); } -// update tailscale status -export function updateTailscaleStatus(command: 'up' | 'down') { - return http.post('/api/network/tailscale/status', { command }); +// run tailscale up +export function upTailscale() { + return http.post('/api/network/tailscale/up'); +} + +// run tailscale down +export function downTailscale() { + return http.post('/api/network/tailscale/down'); } // login tailscale @@ -50,3 +60,12 @@ export function logoutTailscale() { export function getWiFi() { return http.get('/api/network/wifi'); } + +// connect wifi +export function connectWifi(ssid: string, password: string) { + const data = { + ssid, + password + }; + return http.post('/api/network/wifi', data); +} diff --git a/web/src/api/vm.ts b/web/src/api/vm.ts index 9d9ef26..b984b6d 100644 --- a/web/src/api/vm.ts +++ b/web/src/api/vm.ts @@ -27,3 +27,27 @@ export function updateScreen(type: string, value: number) { }; return http.post('/api/vm/screen', data); } + +// get memory limit +export function getMemoryLimit() { + return http.get('/api/vm/memory/limit'); +} + +// set memory limit +export function setMemoryLimit(enabled: boolean, limit: number) { + const data = { + enabled, + limit + }; + return http.post('/api/vm/memory/limit', data); +} + +// get OLED configuration +export function getOLED() { + return http.get('/api/vm/oled'); +} + +// set OLED configuration +export function setOLED(sleep: number) { + return http.post('/api/vm/oled', { sleep }); +} diff --git a/web/src/assets/images/tailscale.svg b/web/src/assets/images/tailscale.svg new file mode 100644 index 0000000..968323b --- /dev/null +++ b/web/src/assets/images/tailscale.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/web/src/components/icons/tailscale.tsx b/web/src/components/icons/tailscale.tsx new file mode 100644 index 0000000..28dcec1 --- /dev/null +++ b/web/src/components/icons/tailscale.tsx @@ -0,0 +1,5 @@ +import icon from '@/assets/images/tailscale.svg'; + +export const Tailscale = () => { + return tailscale; +}; diff --git a/web/src/i18n/locales/cz.ts b/web/src/i18n/locales/cz.ts index 9e56646..5616199 100644 --- a/web/src/i18n/locales/cz.ts +++ b/web/src/i18n/locales/cz.ts @@ -1,25 +1,11 @@ const cz = { translation: { - language: 'Jazyk', - changePassword: 'Změnit heslo', - logout: 'Odhlásit se', - settings: 'Nastavení', - showMouse: 'Zobrazit kurzor myši', - hideMouse: 'Skrýt kurzor myši', - power: 'Napájení', - reset: 'Resetovat', - powerShort: 'Napájení (krátký stisk)', - powerLong: 'Napájení (dlouhý stisk)', - hddLed: 'HDD LED', - checkLibFailed: 'Nepodařilo se zkontrolovat runtime knihovnu, zkuste to prosím znovu', - updateLibFailed: 'Nepodařilo se aktualizovat runtime knihovnu, zkuste to prosím znovu', - updatingLib: 'Aktualizuji runtime knihovnu. Po dokončení aktualizace prosím obnovte stránku.', - checkForUpdate: 'Zkontrolovat aktualizaci', head: { desktop: 'Vzdálená plocha', login: 'Přihlášení', changePassword: 'Změna hesla', - terminal: 'Terminál' + terminal: 'Terminál', + wifi: 'Wi-Fi' }, auth: { login: 'Přihlášení', @@ -39,14 +25,28 @@ const cz = { illegalUsername: 'Uživatelské jméno obsahuje nepovolené znaky', illegalPassword: 'Heslo obsahuje nepovolené znaky', forgetPassword: 'Zapomenuté heslo', - resetPassword: 'Resetovat heslo', - reset1: 'Pokud jste zapomněli heslo, postupujte podle následujících kroků pro reset:', - reset2: '1. Přihlaste se k zařízení NanoKVM pomocí SSH;', - reset3: '2. Smažte soubor v zařízení: ', - reset4: '3. Přihlaste se pomocí výchozího účtu: ', ok: 'OK', cancel: 'Zrušit', - loginButtonText: 'Přihlášení' + loginButtonText: 'Přihlášení', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Režim videa', @@ -132,60 +132,113 @@ const cz = { confirm: 'OK' }, wol: { + title: 'Wake-on-LAN', sending: 'Odesílání příkazu...', sent: 'Příkaz odeslán', input: 'Zadejte prosím MAC adresu', ok: 'OK' }, - about: { - title: 'O NanoKVM', - information: 'Informace', - ip: 'IP', - mdns: 'mDNS', - application: 'Verze aplikace', - image: 'Verze obrazu', - deviceKey: 'Klíč zařízení', - queryFailed: 'Dotaz se nezdařil', - community: 'Komunita' + power: { + title: 'Napájení', + reset: 'Resetovat', + power: 'Napájení', + powerShort: 'Napájení (krátký stisk)', + powerLong: 'Napájení (dlouhý stisk)' }, - update: { - title: 'Zkontrolovat aktualizaci', - queryFailed: 'Nepodařilo se získat verzi', - updateFailed: 'Aktualizace se nezdařila. Zkuste to prosím znovu.', - isLatest: 'Máte nejnovější verzi.', - available: 'Je dostupná aktualizace. Opravdu chcete aktualizovat?', - updating: 'Aktualizace zahájena. Prosím čekejte...', - confirm: 'Potvrdit', - cancel: 'Zrušit' - }, - virtualDevice: { - network: 'Virtuální síť', - disk: 'Virtuální disk' - }, - tailscale: { - loading: 'Načítání...', - notInstall: 'Tailscale nebyl nalezen! Prosím nainstalujte.', - install: 'Nainstalovat', - installing: 'Instalace probíhá', - failed: 'Instalace se nezdařila', - retry: 'Obnovte stránku a zkuste to znovu. Nebo zkuste instalaci manuálně', - download: 'Stáhnout', - package: 'instalační balíček', - unzip: 'a rozbalit ho', - upTailscale: 'Nahrajte Tailscale do adresáře NanoKVM /usr/bin/', - upTailscaled: 'Nahrajte Tailscaled do adresáře NanoKVM /usr/sbin/', - refresh: 'Obnovit stránku', - notLogin: - 'Zařízení nebylo dosud spárováno. Přihlaste se prosím a spárujte toto zařízení s vaším účtem.', - urlPeriod: 'Tento odkaz je platný po dobu 10 minut', - login: 'Přihlášení', - loginSuccess: 'Přihlášení úspěšné', - enable: 'Povolit Tailscale', - deviceName: 'Název zařízení', - deviceIP: 'IP zařízení', - account: 'Účet', - logout: 'Odhlásit se', - logout2: 'Opravdu se chcete odhlásit?' + settings: { + title: 'Nastavení', + about: { + title: 'O NanoKVM', + information: 'Informace', + ip: 'IP', + mdns: 'mDNS', + application: 'Verze aplikace', + applicationTip: 'NanoKVM web application version', + image: 'Verze obrazu', + imageTip: 'NanoKVM system image version', + deviceKey: 'Klíč zařízení', + community: 'Komunita' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Jazyk', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Načítání...', + notInstall: 'Tailscale nebyl nalezen! Prosím nainstalujte.', + install: 'Nainstalovat', + installing: 'Instalace probíhá', + failed: 'Instalace se nezdařila', + retry: 'Obnovte stránku a zkuste to znovu. Nebo zkuste instalaci manuálně', + download: 'Stáhnout', + package: 'instalační balíček', + unzip: 'a rozbalit ho', + upTailscale: 'Nahrajte Tailscale do adresáře NanoKVM /usr/bin/', + upTailscaled: 'Nahrajte Tailscaled do adresáře NanoKVM /usr/sbin/', + refresh: 'Obnovit stránku', + notLogin: + 'Zařízení nebylo dosud spárováno. Přihlaste se prosím a spárujte toto zařízení s vaším účtem.', + urlPeriod: 'Tento odkaz je platný po dobu 10 minut', + login: 'Přihlášení', + loginSuccess: 'Přihlášení úspěšné', + enable: 'Povolit Tailscale', + deviceName: 'Název zařízení', + deviceIP: 'IP zařízení', + account: 'Účet', + logout: 'Odhlásit se', + logout2: 'Opravdu se chcete odhlásit?' + }, + update: { + title: 'Zkontrolovat aktualizaci', + queryFailed: 'Nepodařilo se získat verzi', + updateFailed: 'Aktualizace se nezdařila. Zkuste to prosím znovu.', + isLatest: 'Máte nejnovější verzi.', + available: 'Je dostupná aktualizace. Opravdu chcete aktualizovat?', + updating: 'Aktualizace zahájena. Prosím čekejte...', + confirm: 'Potvrdit', + cancel: 'Zrušit' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/da.ts b/web/src/i18n/locales/da.ts index fa3cb6f..c941151 100644 --- a/web/src/i18n/locales/da.ts +++ b/web/src/i18n/locales/da.ts @@ -1,25 +1,11 @@ const da = { translation: { - language: 'Sprog', - changePassword: 'Skift adgangskode', - logout: 'Log ud', - settings: 'Indstillinger', - showMouse: 'Vis mus', - hideMouse: 'Skjul mus', - power: 'Tænd/sluk-knap', - reset: 'Nulstillingsknap', - powerShort: 'Tænd/sluk-knap (kort tryk)', - powerLong: 'Tænd/sluk-knap (langt tryk)', - hddLed: 'HDD lysdiode', - checkLibFailed: 'Kunne ikke kontrollere runtime-biblioteket. Prøv igen', - updateLibFailed: 'Kunne ikke opdatere runtime-biblioteket. Prøv igen', - updatingLib: 'Opdaterer runtime-biblioteket. Opdater siden efter opdateringen.', - checkForUpdate: 'Kontroller for opdatering', head: { desktop: 'Fjernskrivebord', login: 'Log ind', changePassword: 'Skift adgangskode', - terminal: 'Terminal' + terminal: 'Terminal', + wifi: 'Wi-Fi' }, auth: { login: 'Log ind', @@ -38,14 +24,28 @@ const da = { illegalUsername: 'brugernavn indeholder ugyldige tegn', illegalPassword: 'adgangskode indeholder ugyldige tegn', forgetPassword: 'Glem adgangskode', - resetPassword: 'Nulstil adgangskode', - reset1: 'Hvis du har glemt adgangskoden, kan du følge disse trin for at nulstille den:', - reset2: '1. Log ind på din NanoKVM via SSH.', - reset3: '2. Slet filen på enheden: ', - reset4: '3. Brug standardkontoen til at logge ind: ', ok: 'OK', cancel: 'Annuller', - loginButtonText: 'Log ind' + loginButtonText: 'Log ind', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Videotilstand', @@ -130,60 +130,113 @@ const da = { confirm: 'OK' }, wol: { + title: 'Wake-on-LAN', sending: 'Sender Wake-on-LAN magic packet', sent: 'Wake-on-LAN magic packet sendt', input: 'Angiv MAC-adresse', ok: 'OK' }, - about: { - title: 'Om NanoKVM', - information: 'Information', - ip: 'IP', - mdns: 'mDNS', - application: 'Program version', - image: 'Firmware version', - deviceKey: 'Enhedsnøgle', - queryFailed: 'Forespørgsel mislykkedes', - community: 'Fællesskab' + power: { + title: 'Tænd/sluk-knap', + reset: 'Nulstillingsknap', + power: 'Tænd/sluk-knap', + powerShort: 'Tænd/sluk-knap (kort tryk)', + powerLong: 'Tænd/sluk-knap (langt tryk)' }, - update: { - title: 'Kontroller for opdatering', - queryFailed: 'Opdateringskontrol mislykkedes', - updateFailed: 'Opdatering fejlede. Prøv igen.', - isLatest: 'Du har allerede den nyeste version.', - available: 'En opdatering er tilgængelig. Vil du installere den?', - updating: 'Opdatering i gang. Vent venligst...', - confirm: 'Bekræft', - cancel: 'Annuller' - }, - virtualDevice: { - network: 'Virtuelt netværk', - disk: 'Virtuel disk' - }, - tailscale: { - loading: 'Indlæser...', - notInstall: 'Tailscale ikke fundet! Installer det for at fuldføre opsætningen.', - install: 'Installer', - installing: 'Installerer', - failed: 'Installation mislykkedes', - retry: 'Opdater siden og prøv igen. Ellers prøv at installere manuelt.', - download: 'Download', - package: 'installationspakken', - unzip: 'og udpak den', - upTailscale: 'Upload tailscale til følgende mappe på enheden: /usr/bin/', - upTailscaled: 'Upload tailscaled til følgende mappe på enheden: /usr/sbin/', - refresh: 'Opdater sides', - notLogin: - 'Enheden er ikke tilknyttet en Tailscale-konto endnu. Log ind for at fuldføre tilknytningen til din konto.', - urlPeriod: 'Denne URL er gyldig i 10 minutter', - login: 'Log ind', - loginSuccess: 'Log ind lykkedes', - enable: 'Aktiver Tailscale', - deviceName: 'Enhedens navn', - deviceIP: 'Enhedens IP', - account: 'Konto', - logout: 'Log ud', - logout2: 'Er du sikker på at du vil logge ud?' + settings: { + title: 'Settings', + about: { + title: 'Om NanoKVM', + information: 'Information', + ip: 'IP', + mdns: 'mDNS', + application: 'Program version', + applicationTip: 'NanoKVM web application version', + image: 'Firmware version', + imageTip: 'NanoKVM system image version', + deviceKey: 'Enhedsnøgle', + community: 'Fællesskab' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Indlæser...', + notInstall: 'Tailscale ikke fundet! Installer det for at fuldføre opsætningen.', + install: 'Installer', + installing: 'Installerer', + failed: 'Installation mislykkedes', + retry: 'Opdater siden og prøv igen. Ellers prøv at installere manuelt.', + download: 'Download', + package: 'installationspakken', + unzip: 'og udpak den', + upTailscale: 'Upload tailscale til følgende mappe på enheden: /usr/bin/', + upTailscaled: 'Upload tailscaled til følgende mappe på enheden: /usr/sbin/', + refresh: 'Opdater sides', + notLogin: + 'Enheden er ikke tilknyttet en Tailscale-konto endnu. Log ind for at fuldføre tilknytningen til din konto.', + urlPeriod: 'Denne URL er gyldig i 10 minutter', + login: 'Log ind', + loginSuccess: 'Log ind lykkedes', + enable: 'Aktiver Tailscale', + deviceName: 'Enhedens navn', + deviceIP: 'Enhedens IP', + account: 'Konto', + logout: 'Log ud', + logout2: 'Er du sikker på at du vil logge ud?' + }, + update: { + title: 'Kontroller for opdatering', + queryFailed: 'Opdateringskontrol mislykkedes', + updateFailed: 'Opdatering fejlede. Prøv igen.', + isLatest: 'Du har allerede den nyeste version.', + available: 'En opdatering er tilgængelig. Vil du installere den?', + updating: 'Opdatering i gang. Vent venligst...', + confirm: 'Bekræft', + cancel: 'Annuller' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/de.ts b/web/src/i18n/locales/de.ts index a631e4f..a628022 100644 --- a/web/src/i18n/locales/de.ts +++ b/web/src/i18n/locales/de.ts @@ -1,26 +1,11 @@ const de = { translation: { - language: 'Sprache', - changePassword: 'Passwort ändern', - logout: 'Ausloggen', - settings: 'Einstellungen', - showMouse: 'Mauszeiger anzeigen', - hideMouse: 'Mauszeiger verstecken', - power: 'Einschalten', - reset: 'Neustart', - powerShort: 'Einschalten (Kurzes Drücken)', - powerLong: 'Einschalten (Langes Drücken)', - hddLed: 'Festplatten-LED', - checkLibFailed: 'Überprüfung der Laufzeitbibliothek fehlgeschlagen, bitte erneut versuchen.', - updateLibFailed: - 'Aktualisierung der Laufzeitbibliothek fehlgeschlagen, bitte erneut versuchen.', - updatingLib: 'Aktualiserung der Laufzeitbibliothek. Bitte nach dem Update die Seite neu laden.', - checkForUpdate: 'Nach Update suchen', head: { desktop: 'Entfernter Desktop', login: 'Einloggen', changePassword: 'Passwort ändern', - terminal: 'Terminal' + terminal: 'Terminal', + wifi: 'Wi-Fi' }, auth: { login: 'Einloggen', @@ -40,15 +25,28 @@ const de = { illegalUsername: 'Benutzername beinhaltet ungültige Zeichen', illegalPassword: 'Passwort beinhaltet ungültige Zeichen', forgetPassword: 'Passwort vergessen', - resetPassword: 'Password zurücksetzen', - reset1: - 'Falls sie das Passwort vergessen haben, führen sie folgende Schritte durch um dieses zurückzusetzen:', - reset2: '1. In das NanoKVM via SSH einloggen;', - reset3: '2. Die Datei auf dem Gerät löschen: ', - reset4: '3. Den Standardaccount zum Einloggen benutzen: ', ok: 'Ok', cancel: 'Abbrechen', - loginButtonText: 'Einloggen' + loginButtonText: 'Einloggen', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Videomodus', @@ -134,62 +132,115 @@ const de = { confirm: 'Ok' }, wol: { + title: 'Wake-on-LAN', sending: 'Befehl senden...', sent: 'Befehl gesendet', input: 'Bitte die MAC eingeben', ok: 'Ok' }, - about: { - title: 'Über NanoKVM', - information: 'Information', - ip: 'IP', - mdns: 'mDNS', - application: 'Versionsnummer', - image: 'Image Version', - deviceKey: 'Geräte-Key', - queryFailed: 'Anfrage gescheitert', - community: 'Community' + power: { + title: 'Einschalten', + reset: 'Neustart', + power: 'Einschalten', + powerShort: 'Einschalten (Kurzes Drücken)', + powerLong: 'Einschalten (Langes Drücken)' }, - update: { - title: 'Nach neuem Update suchen', - queryFailed: 'Versionsnummer konnte nicht erkannt werden', - updateFailed: 'Update gescheitert. Bitte versuchen sie es erneut.', - isLatest: 'Die aktuelle Version ist bereits installiert.', - available: - 'Ein Update ist verfügbar. Sind sie sicher, dass sie diese Version aktualisieren möchten?', - updating: 'Update wird gestartet. Bitte warten...', - confirm: 'Bestätigen', - cancel: 'Abbrechen' - }, - virtualDevice: { - network: 'Virtuelles Netzwerk', - disk: 'Virtuelle Festplatte' - }, - tailscale: { - loading: 'Lade...', - notInstall: 'Tailscale wurde nicht gefunden! Bitte installieren.', - install: 'Installieren', - installing: 'Installation wird durchgeführt', - failed: 'Installation gescheitert', - retry: - 'Bitte neu laden und noch einmal versuchen. Oder versuchen sie Tailscale manuell zu installieren', - download: 'Laden sie das', - package: 'Installationspaket', - unzip: 'und entpacken sie es manuell', - upTailscale: 'Hochladen von tailscale in das NanoKVM Verzeichnis /usr/bin/', - upTailscaled: 'Hochladen von tailscaled in das NanoKVM Verzeichnis /usr/sbin/', - refresh: 'Die aktuelle Seite neu laden', - notLogin: - 'Diese Geräte ist bisher noch nicht verknüpft. Bitte loggen sie sich in ihr Konto ein und verknüpfen sie dieses Gerät mit diesem.', - urlPeriod: 'Diese URL ist für 10 Minuten gültig', - login: 'Einloggen', - loginSuccess: 'Das Einloggen war erfolgreich', - enable: 'Tailscale aktivieren', - deviceName: 'Name des Geräts', - deviceIP: 'Geräte-IP', - account: 'Benutzerkonto', - logout: 'Ausloggen', - logout2: 'Wollen Sie sich wirklich ausloggen?' + settings: { + title: 'Settings', + about: { + title: 'Über NanoKVM', + information: 'Information', + ip: 'IP', + mdns: 'mDNS', + application: 'Versionsnummer', + applicationTip: 'NanoKVM web application version', + image: 'Image Version', + imageTip: 'NanoKVM system image version', + deviceKey: 'Geräte-Key', + community: 'Community' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Lade...', + notInstall: 'Tailscale wurde nicht gefunden! Bitte installieren.', + install: 'Installieren', + installing: 'Installation wird durchgeführt', + failed: 'Installation gescheitert', + retry: + 'Bitte neu laden und noch einmal versuchen. Oder versuchen sie Tailscale manuell zu installieren', + download: 'Laden sie das', + package: 'Installationspaket', + unzip: 'und entpacken sie es manuell', + upTailscale: 'Hochladen von tailscale in das NanoKVM Verzeichnis /usr/bin/', + upTailscaled: 'Hochladen von tailscaled in das NanoKVM Verzeichnis /usr/sbin/', + refresh: 'Die aktuelle Seite neu laden', + notLogin: + 'Diese Geräte ist bisher noch nicht verknüpft. Bitte loggen sie sich in ihr Konto ein und verknüpfen sie dieses Gerät mit diesem.', + urlPeriod: 'Diese URL ist für 10 Minuten gültig', + login: 'Einloggen', + loginSuccess: 'Das Einloggen war erfolgreich', + enable: 'Tailscale aktivieren', + deviceName: 'Name des Geräts', + deviceIP: 'Geräte-IP', + account: 'Benutzerkonto', + logout: 'Ausloggen', + logout2: 'Wollen Sie sich wirklich ausloggen?' + }, + update: { + title: 'Nach neuem Update suchen', + queryFailed: 'Versionsnummer konnte nicht erkannt werden', + updateFailed: 'Update gescheitert. Bitte versuchen sie es erneut.', + isLatest: 'Die aktuelle Version ist bereits installiert.', + available: + 'Ein Update ist verfügbar. Sind sie sicher, dass sie diese Version aktualisieren möchten?', + updating: 'Update wird gestartet. Bitte warten...', + confirm: 'Bestätigen', + cancel: 'Abbrechen' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index 440dc09..f7e1a19 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -1,25 +1,11 @@ const en = { translation: { - language: 'Language', - changePassword: 'Change Password', - logout: 'Logout', - settings: 'Settings', - showMouse: 'Show mouse', - hideMouse: 'Hide mouse', - power: 'Power', - reset: 'Reset', - powerShort: 'Power (short click)', - powerLong: 'Power (long click)', - hddLed: 'HDD LED', - checkLibFailed: 'Failed to check runtime library, please try again', - updateLibFailed: 'Failed to update runtime library, please try again', - updatingLib: 'Updating runtime library. Please refresh the page after updating.', - checkForUpdate: 'Check for Update', head: { desktop: 'Remote Desktop', login: 'Login', changePassword: 'Change Password', - terminal: 'Terminal' + terminal: 'Terminal', + wifi: 'Wi-Fi' }, auth: { login: 'Login', @@ -32,19 +18,33 @@ const en = { invalidUser: 'Invalid username or password', error: 'Unexpected error', changePassword: 'Change Password', - changePasswordDesc: 'For the security of your device, please modify the web login password.', + changePasswordDesc: 'For the security of your device, please change the password!', differentPassword: 'Passwords do not match', illegalUsername: 'Username contains illegal characters', illegalPassword: 'Password contains illegal characters', forgetPassword: 'Forgot Password', - resetPassword: 'Reset Password', - reset1: 'If you have forgotten the password, please follow the steps to reset it:', - reset2: '1. Login to the NanoKVM device via SSH;', - reset3: '2. Delete the file in the device: ', - reset4: '3. Use the default account to login: ', ok: 'Ok', cancel: 'Cancel', loginButtonText: 'Login', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Video Mode', @@ -84,7 +84,7 @@ const en = { resetHid: 'Reset HID' }, image: { - title: 'Images', + title: 'Image', loading: 'Loading...', empty: 'Nothing Found', mountFailed: 'Mount Failed', @@ -129,60 +129,113 @@ const en = { confirm: 'Ok' }, wol: { + title: 'Wake-on-LAN', sending: 'Sending command...', sent: 'Command sent', input: 'Please enter the MAC', ok: 'Ok' }, - about: { - title: 'About NanoKVM', - information: 'Information', - ip: 'IP', - mdns: 'mDNS', - application: 'Application Version', - image: 'Image Version', - deviceKey: 'Device Key', - queryFailed: 'Query failed', - community: 'Community' + power: { + title: 'Power', + reset: 'Reset', + power: 'Power', + powerShort: 'Power (short click)', + powerLong: 'Power (long click)' }, - update: { - title: 'Check for Update', - queryFailed: 'Get version failed', - updateFailed: 'Update failed. Please retry.', - isLatest: 'You already have the latest version.', - available: 'An update is available. Are you sure to update?', - updating: 'Update started. Please wait...', - confirm: 'Confirm', - cancel: 'Cancel' - }, - virtualDevice: { - network: 'Virtual Network', - disk: 'Virtual Disk' - }, - tailscale: { - loading: 'Loading...', - notInstall: 'Tailscale not found! Please install.', - install: 'Install', - installing: 'Installing', - failed: 'Install failed', - retry: 'Please refresh and try again. Or try to install manually', - download: 'Download the', - package: 'installation package', - unzip: 'and unzip it', - upTailscale: 'Upload tailscale to NanoKVM directory /usr/bin/', - upTailscaled: 'Upload tailscaled to NanoKVM directory /usr/sbin/', - refresh: 'Refresh current page', - notLogin: - 'The device has not been bound yet. Please login and bind this device to your account.', - urlPeriod: 'This url is valid for 10 minutes', - login: 'Login', - loginSuccess: 'Login Success', - enable: 'Enable Tailscale', - deviceName: 'Device Name', - deviceIP: 'Device IP', - account: 'Account', - logout: 'Logout', - logout2: 'Sure to logout?' + settings: { + title: 'Settings', + about: { + title: 'About NanoKVM', + information: 'Information', + ip: 'IP', + mdns: 'mDNS', + application: 'Application Version', + applicationTip: 'NanoKVM web application version', + image: 'Image Version', + imageTip: 'NanoKVM system image version', + deviceKey: 'Device Key', + community: 'Community' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Loading...', + notInstall: 'Tailscale not found! Please install.', + install: 'Install', + installing: 'Installing', + failed: 'Install failed', + retry: 'Please refresh and try again. Or try to install manually', + download: 'Download the', + package: 'installation package', + unzip: 'and unzip it', + upTailscale: 'Upload tailscale to NanoKVM directory /usr/bin/', + upTailscaled: 'Upload tailscaled to NanoKVM directory /usr/sbin/', + refresh: 'Refresh current page', + notLogin: + 'The device has not been bound yet. Please login and bind this device to your account.', + urlPeriod: 'This url is valid for 10 minutes', + login: 'Login', + loginSuccess: 'Login Success', + enable: 'Enable Tailscale', + deviceName: 'Device Name', + deviceIP: 'Device IP', + account: 'Account', + logout: 'Logout', + logout2: 'Sure to logout?' + }, + update: { + title: 'Check for Updates', + queryFailed: 'Get version failed', + updateFailed: 'Update failed. Please retry.', + isLatest: 'You already have the latest version.', + available: 'An update is available. Are you sure to update?', + updating: 'Update started. Please wait...', + confirm: 'Confirm', + cancel: 'Cancel' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/es.ts b/web/src/i18n/locales/es.ts index 971719b..fdd8afc 100644 --- a/web/src/i18n/locales/es.ts +++ b/web/src/i18n/locales/es.ts @@ -1,25 +1,11 @@ const en = { translation: { - language: 'Idioma', - changePassword: 'Cambiar contraseña', - logout: 'Cerrar sesión', - settings: 'Ajustes', - showMouse: 'Mostrar ratón', - hideMouse: 'Ocultar ratón', - power: 'Encender / Apagar', - reset: 'Reiniciar', - powerShort: 'Encender / Apagar (pulsación corta)', - powerLong: 'Encender / Apagar (pulsación larga)', - hddLed: 'LED HDD', - checkLibFailed: 'Error al comprobar la aplicación. Por favor, vuelve a intentarlo.', - updateLibFailed: 'Error al comprobar la aplicación. Por favor, vuelve a intentarlo.', - updatingLib: 'Actualizando aplicación. Por favor, recarga la página después de actualizar.', - checkForUpdate: 'Comprobar actualizaciones', head: { desktop: 'Escritorio remoto', login: 'Inicio de sesión', changePassword: 'Cambiar contraseña', - terminal: 'Consola' + terminal: 'Consola', + wifi: 'Wi-Fi' }, auth: { login: 'Iniciar sesión', @@ -39,15 +25,28 @@ const en = { illegalUsername: 'El usuario contiene caracteres no permitidos', illegalPassword: 'La contraseña contiene caracteres no permitidos', forgetPassword: 'Contraseña olvidada', - resetPassword: 'Reiniciar contraseña', - reset1: - 'Si has olvidado tu contraseña, por favor, sigue los siguientes pasos para recuperarla:', - reset2: '1. Inicia sesión en el dispositivo NanoKVM a través de SSH', - reset3: '2. Elimina este archivo en el dispositivo: ', - reset4: '3. Utiliza la cuenta por defecto para iniciar sesión: ', ok: 'Aceptar', cancel: 'Cancelar', - loginButtonText: 'Iniciar sesión' + loginButtonText: 'Iniciar sesión', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Modo de vídeo', @@ -133,61 +132,114 @@ const en = { confirm: 'Confirmar' }, wol: { + title: 'Wake-on-LAN', sending: 'Enviando comando...', sent: 'Comando enviado', input: 'Por favor, introduce la dirección MAC', ok: 'Vale' }, - about: { - title: 'Sobre NanoKVM', - information: 'Información', - ip: 'IP', - mdns: 'mDNS', - application: 'Versión de la aplicación', - image: 'Versión de la imagen', - deviceKey: 'Clave del dispositivo', - queryFailed: 'Consulta fallida', - community: 'Comunidad' + power: { + title: 'Encender / Apagar', + reset: 'Reiniciar', + power: 'Encender / Apagar', + powerShort: 'Encender / Apagar (pulsación corta)', + powerLong: 'Encender / Apagar (pulsación larga)' }, - update: { - title: 'Buscar actualizaciones', - queryFailed: 'Error al obtener la versión', - updateFailed: 'La actualización falló. Por favor, inténtalo de nuevo.', - isLatest: 'Ya tienes la última versión.', - available: 'Hay una actualización disponible. ¿Estás seguro de que quieres actualizar?', - updating: 'Actualización iniciada. Por favor, espera...', - confirm: 'Confirmar', - cancel: 'Cancelar' - }, - virtualDevice: { - network: 'Red Virtual', - disk: 'Disco Virtual' - }, - tailscale: { - loading: 'Cargando...', - notInstall: '¡Tailscale no encontrado! Por favor, instálalo.', - install: 'Instalar', - installing: 'Instalando', - failed: 'La instalación falló', - retry: - 'Por favor, actualiza la página e inténtalo de nuevo. O intenta instalarlo manualmente', - download: 'Descargar el', - package: 'paquete de instalación', - unzip: 'y descomprimirlo', - upTailscale: 'Sube tailscale al directorio /usr/bin/ del NanoKVM', - upTailscaled: 'Sube tailscaled al directorio /usr/sbin/ del NanoKVM', - refresh: 'Actualizar la página actual', - notLogin: - 'El dispositivo aún no ha sido vinculado. Por favor, inicia sesión y vincula este dispositivo a tu cuenta.', - urlPeriod: 'Esta URL es válida por 10 minutos', - login: 'Iniciar sesión', - loginSuccess: 'Inicio de sesión exitoso', - enable: 'Habilitar Tailscale', - deviceName: 'Nombre del dispositivo', - deviceIP: 'IP del dispositivo', - account: 'Cuenta', - logout: 'Cerrar sesión', - logout2: '¿Seguro que quieres cerrar sesión?' + settings: { + title: 'Settings', + about: { + title: 'Sobre NanoKVM', + information: 'Información', + ip: 'IP', + mdns: 'mDNS', + application: 'Versión de la aplicación', + applicationTip: 'NanoKVM web application version', + image: 'Versión de la imagen', + imageTip: 'NanoKVM system image version', + deviceKey: 'Clave del dispositivo', + community: 'Comunidad' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Cargando...', + notInstall: '¡Tailscale no encontrado! Por favor, instálalo.', + install: 'Instalar', + installing: 'Instalando', + failed: 'La instalación falló', + retry: + 'Por favor, actualiza la página e inténtalo de nuevo. O intenta instalarlo manualmente', + download: 'Descargar el', + package: 'paquete de instalación', + unzip: 'y descomprimirlo', + upTailscale: 'Sube tailscale al directorio /usr/bin/ del NanoKVM', + upTailscaled: 'Sube tailscaled al directorio /usr/sbin/ del NanoKVM', + refresh: 'Actualizar la página actual', + notLogin: + 'El dispositivo aún no ha sido vinculado. Por favor, inicia sesión y vincula este dispositivo a tu cuenta.', + urlPeriod: 'Esta URL es válida por 10 minutos', + login: 'Iniciar sesión', + loginSuccess: 'Inicio de sesión exitoso', + enable: 'Habilitar Tailscale', + deviceName: 'Nombre del dispositivo', + deviceIP: 'IP del dispositivo', + account: 'Cuenta', + logout: 'Cerrar sesión', + logout2: '¿Seguro que quieres cerrar sesión?' + }, + update: { + title: 'Buscar actualizaciones', + queryFailed: 'Error al obtener la versión', + updateFailed: 'La actualización falló. Por favor, inténtalo de nuevo.', + isLatest: 'Ya tienes la última versión.', + available: 'Hay una actualización disponible. ¿Estás seguro de que quieres actualizar?', + updating: 'Actualización iniciada. Por favor, espera...', + confirm: 'Confirmar', + cancel: 'Cancelar' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/fr.ts b/web/src/i18n/locales/fr.ts index 9609022..b8b14ff 100644 --- a/web/src/i18n/locales/fr.ts +++ b/web/src/i18n/locales/fr.ts @@ -1,26 +1,11 @@ const fr = { translation: { - language: 'Langue', - changePassword: 'Changer le mot de passe', - logout: 'Déconnexion', - settings: 'Réglages', - showMouse: 'Afficher la souris', - hideMouse: 'Masquer la souris', - power: 'Power', - reset: 'Reset', - powerShort: 'Power (appui court)', - powerLong: 'Power (appui long)', - hddLed: 'HDD LED', - checkLibFailed: "Impossible de vérifier la bibliothèque d'exécution, veuillez réessayer", - updateLibFailed: "Impossible de mettre à jour la bibliothèque d'exécution, veuillez réessayer", - updatingLib: - "Mise à jour de la bibliothèque d'exécution. Veuillez rafraîchir la page après la mise à jour.", - checkForUpdate: 'Chercher des mises à jour', head: { desktop: 'Bureau à distance', login: 'Connexion', changePassword: 'Changer le mot de passe', - terminal: 'Terminal' + terminal: 'Terminal', + wifi: 'Wi-Fi' }, auth: { login: 'Connexion', @@ -40,15 +25,28 @@ const fr = { illegalUsername: "Le nom d'utilisateur contient des caractères illégaux", illegalPassword: 'Le mot de passe contient des caractères illégaux', forgetPassword: 'Mot de passe oublié', - resetPassword: 'Réinitialiser le mot de passe', - reset1: - 'Si vous avez oublié le mot de passe, veuillez suivre les étapes pour le réinitialiser:', - reset2: "1. Connectez-vous à l'appareil NanoKVM via SSH;", - reset3: "2. Supprimez le fichier de l'appareil: ", - reset4: '3. Utilisez le compte par défaut pour vous connecter: ', ok: 'Se connecter', cancel: 'Annuler', - loginButtonText: 'Connexion' + loginButtonText: 'Connexion', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Mode vidéo', @@ -134,59 +132,112 @@ const fr = { confirm: 'Ok' }, wol: { + title: 'Wake-on-LAN', sending: 'Envoi de la commande...', sent: 'Commande envoyée', input: "Veuillez entrer l'adresse MAC", ok: 'Ok' }, - about: { - title: 'A propos de NanoKVM', - information: 'Informations', - ip: 'IP', - mdns: 'mDNS', - application: "Version de l'application", - image: "Version de l'image", - deviceKey: "Clé de l'appareil", - queryFailed: 'Echec de la requête', - community: 'Communauté' + power: { + title: 'Power', + reset: 'Reset', + power: 'Power', + powerShort: 'Power (appui court)', + powerLong: 'Power (appui long)' }, - update: { - title: 'Vérifier les mises à jour', - queryFailed: 'Impossible de vérifier les mises à jour. Veuillez réessayer.', - updateFailed: 'Mis à jour échouée. Veuillez réessayer.', - isLatest: 'Vous avez déjà la dernière version.', - available: 'Une mise à jour est disponible. Voulez-vous vraiment mettre à jour?', - updating: 'Mise à jour en cours. Veuillez patienter...', - confirm: 'Confirmer', - cancel: 'Annuler' - }, - virtualDevice: { - network: 'carte réseau virtuelle', - disk: 'disque virtuel' - }, - tailscale: { - loading: 'Chargement...', - notInstall: "Tailscale non trouvé! Veuillez l'installer.", - install: 'Installer', - installing: 'Installation', - failed: 'Installation échouée', - retry: "Veuillez rafraîchir et réessayer. Ou essayez d'installer manuellement", - download: 'Télécharger le', - package: 'installation package', - unzip: 'et décompressez-le', - upTailscale: 'Téléverser tailscale dans le répertoire NanoKVM /usr/sbin/', - upTailscaled: 'Téléverser tailscaled dans le répertoire NanoKVM /usr/sbin/', - refresh: 'Rafraîchir la page courante', - notLogin: "L'appareil n'est pas relié. Connectez-vous et liez cet appareil à votre compte.", - urlPeriod: "L'URL est valide pendant 10 minutes", - login: 'Connexion', - loginSuccess: 'Connexion réussie', - enable: 'Démarrer Tailscale', - deviceName: "Nom de l'appareil", - deviceIP: "IP de l'appareil", - account: 'Compte', - logout: 'Déconnexion', - logout2: 'Voulez-vous vous déconnecter?' + settings: { + title: 'Settings', + about: { + title: 'A propos de NanoKVM', + information: 'Informations', + ip: 'IP', + mdns: 'mDNS', + application: "Version de l'application", + applicationTip: 'NanoKVM web application version', + image: "Version de l'image", + imageTip: 'NanoKVM system image version', + deviceKey: "Clé de l'appareil", + community: 'Communauté' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Chargement...', + notInstall: "Tailscale non trouvé! Veuillez l'installer.", + install: 'Installer', + installing: 'Installation', + failed: 'Installation échouée', + retry: "Veuillez rafraîchir et réessayer. Ou essayez d'installer manuellement", + download: 'Télécharger le', + package: 'installation package', + unzip: 'et décompressez-le', + upTailscale: 'Téléverser tailscale dans le répertoire NanoKVM /usr/sbin/', + upTailscaled: 'Téléverser tailscaled dans le répertoire NanoKVM /usr/sbin/', + refresh: 'Rafraîchir la page courante', + notLogin: "L'appareil n'est pas relié. Connectez-vous et liez cet appareil à votre compte.", + urlPeriod: "L'URL est valide pendant 10 minutes", + login: 'Connexion', + loginSuccess: 'Connexion réussie', + enable: 'Démarrer Tailscale', + deviceName: "Nom de l'appareil", + deviceIP: "IP de l'appareil", + account: 'Compte', + logout: 'Déconnexion', + logout2: 'Voulez-vous vous déconnecter?' + }, + update: { + title: 'Vérifier les mises à jour', + queryFailed: 'Impossible de vérifier les mises à jour. Veuillez réessayer.', + updateFailed: 'Mis à jour échouée. Veuillez réessayer.', + isLatest: 'Vous avez déjà la dernière version.', + available: 'Une mise à jour est disponible. Voulez-vous vraiment mettre à jour?', + updating: 'Mise à jour en cours. Veuillez patienter...', + confirm: 'Confirmer', + cancel: 'Annuler' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/hu.ts b/web/src/i18n/locales/hu.ts index a27f299..062072e 100644 --- a/web/src/i18n/locales/hu.ts +++ b/web/src/i18n/locales/hu.ts @@ -1,26 +1,11 @@ const hu = { translation: { - language: 'Nyelv', - changePassword: 'Jelszó megváltoztatása', - logout: 'Kijelentkezés', - settings: 'Beállítások', - showMouse: 'Egér mutatása', - hideMouse: 'Egér elrejtése', - power: 'Bekapcsolás', - reset: 'Újraindítás', - powerShort: 'Bekapcsolás (rövid kattintás)', - powerLong: 'Bekapcsolás (hosszú kattintás)', - hddLed: 'HDD LED', - checkLibFailed: 'Nem sikerült ellenőrizni a futási könyvtárat, próbálja újra', - updateLibFailed: 'Nem sikerült frissíteni a futási könyvtárat, próbálja újra', - updatingLib: - 'A futási könyvtár frissítése folyamatban. Kérem, frissítse az oldalt a frissítés után.', - checkForUpdate: 'Frissítés keresése', head: { desktop: 'Távoli Asztal', login: 'Bejelentkezés', changePassword: 'Jelszó megváltoztatása', - terminal: 'Terminál' + terminal: 'Terminál', + wifi: 'Wi-Fi' }, auth: { login: 'Bejelentkezés', @@ -40,14 +25,28 @@ const hu = { illegalUsername: 'A felhasználónév illegális karaktereket tartalmaz', illegalPassword: 'A jelszó illegális karaktereket tartalmaz', forgetPassword: 'Jelszó-emlékeztető', - resetPassword: 'Jelszó alaphelyzetbe állítása', - reset1: 'Ha elfelejtette a jelszót, kövesse az alábbi lépéseket:', - reset2: '1. Jelentkezzen be a NanoKVM eszközre SSH-n keresztül;', - reset3: '2. Törölje a fájlt az eszközről:', - reset4: '3. Használja az alapértelmezett fiókot a bejelentkezéshez:', ok: 'Ok', cancel: 'Mégse', - loginButtonText: 'Bejelentkezés' + loginButtonText: 'Bejelentkezés', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Videó mód', @@ -133,60 +132,113 @@ const hu = { confirm: 'Ok' }, wol: { + title: 'Wake-on-LAN', sending: 'Parancs küldése...', sent: 'Parancs elküldve', input: 'Adja meg a MAC címet', ok: 'Ok' }, - about: { - title: 'NanoKVM Névjegy', - information: 'Információ', - ip: 'IP', - mdns: 'mDNS', - application: 'Alkalmazás verzió', - image: 'Képfájl verzió', - deviceKey: 'Eszköz kulcs', - queryFailed: 'Lekérdezés sikertelen', - community: 'Közösség' + power: { + title: 'Bekapcsolás', + power: 'Bekapcsolás', + reset: 'Újraindítás', + powerShort: 'Bekapcsolás (rövid kattintás)', + powerLong: 'Bekapcsolás (hosszú kattintás)' }, - update: { - title: 'Frissítés keresése', - queryFailed: 'Verzió lekérdezése sikertelen', - updateFailed: 'Frissítés sikertelen. Kérem, próbálja újra.', - isLatest: 'Ön már a legfrissebb verziót használja.', - available: 'Frissítés elérhető. Biztos, hogy frissít?', - updating: 'Frissítés elkezdődött. Kérem várjon...', - confirm: 'Megerősítés', - cancel: 'Mégse' - }, - virtualDevice: { - network: 'Virtuális hálózat', - disk: 'Virtuális lemez' - }, - tailscale: { - loading: 'Betöltés...', - notInstall: 'Tailscale nem található! Kérem, telepítse.', - install: 'Telepítés', - installing: 'Telepítés folyamatban', - failed: 'Telepítés sikertelen', - retry: 'Frissítse az oldalt, majd próbálja újra. Vagy próbálja meg manuálisan telepíteni.', - download: 'Letöltés a', - package: 'telepítési csomag', - unzip: 'és kicsomagolás', - upTailscale: 'Töltsön fel tailscale-t a NanoKVM /usr/bin/ könyvtárába', - upTailscaled: 'Töltsön fel tailscaled-t a NanoKVM /usr/sbin/ könyvtárába', - refresh: 'Frissítse az aktuális oldalt', - notLogin: - 'Az eszköz még nincs kötve. Kérem, jelentkezzen be és kösse az eszközt a fiókjához.', - urlPeriod: 'Ez az url 10 percig érvényes', - login: 'Bejelentkezés', - loginSuccess: 'Sikeres bejelentkezés', - enable: 'Tailscale engedélyezése', - deviceName: 'Eszköz neve', - deviceIP: 'Eszköz IP', - account: 'Fiók', - logout: 'Kijelentkezés', - logout2: 'Biztos, hogy kijelentkezik?' + settings: { + title: 'Settings', + about: { + title: 'NanoKVM Névjegy', + information: 'Információ', + ip: 'IP', + mdns: 'mDNS', + application: 'Alkalmazás verzió', + applicationTip: 'NanoKVM web application version', + image: 'Képfájl verzió', + imageTip: 'NanoKVM system image version', + deviceKey: 'Eszköz kulcs', + community: 'Közösség' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Betöltés...', + notInstall: 'Tailscale nem található! Kérem, telepítse.', + install: 'Telepítés', + installing: 'Telepítés folyamatban', + failed: 'Telepítés sikertelen', + retry: 'Frissítse az oldalt, majd próbálja újra. Vagy próbálja meg manuálisan telepíteni.', + download: 'Letöltés a', + package: 'telepítési csomag', + unzip: 'és kicsomagolás', + upTailscale: 'Töltsön fel tailscale-t a NanoKVM /usr/bin/ könyvtárába', + upTailscaled: 'Töltsön fel tailscaled-t a NanoKVM /usr/sbin/ könyvtárába', + refresh: 'Frissítse az aktuális oldalt', + notLogin: + 'Az eszköz még nincs kötve. Kérem, jelentkezzen be és kösse az eszközt a fiókjához.', + urlPeriod: 'Ez az url 10 percig érvényes', + login: 'Bejelentkezés', + loginSuccess: 'Sikeres bejelentkezés', + enable: 'Tailscale engedélyezése', + deviceName: 'Eszköz neve', + deviceIP: 'Eszköz IP', + account: 'Fiók', + logout: 'Kijelentkezés', + logout2: 'Biztos, hogy kijelentkezik?' + }, + update: { + title: 'Frissítés keresése', + queryFailed: 'Verzió lekérdezése sikertelen', + updateFailed: 'Frissítés sikertelen. Kérem, próbálja újra.', + isLatest: 'Ön már a legfrissebb verziót használja.', + available: 'Frissítés elérhető. Biztos, hogy frissít?', + updating: 'Frissítés elkezdődött. Kérem várjon...', + confirm: 'Megerősítés', + cancel: 'Mégse' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/id.ts b/web/src/i18n/locales/id.ts index d550bde..cd8c780 100644 --- a/web/src/i18n/locales/id.ts +++ b/web/src/i18n/locales/id.ts @@ -1,25 +1,11 @@ const id = { translation: { - language: 'Bahasa', - changePassword: 'Ubah Sandi', - logout: 'Keluar', - settings: 'Pengaturan', - showMouse: 'Tampilkan tetikus', - hideMouse: 'Sembunyikan tetikus', - power: 'Daya', - reset: 'Mulai Ulang', - powerShort: 'Data (tekan sebentar)', - powerLong: 'Power (tekan lama)', - hddLed: 'HDD LED', - checkLibFailed: 'Gagal memeriksa runtime library, coba lagi nanti', - updateLibFailed: 'Gagal memperbarui runtime library, mohon coba lagi', - updatingLib: 'Memperbarui runtime library. Silahkan segarkan halaman setelah memperbarui.', - checkForUpdate: 'Periksa pembaruan', head: { desktop: 'Remote Desktop', login: 'Masuk', changePassword: 'Ubah Sandi', - terminal: 'Terminal' + terminal: 'Terminal', + wifi: 'Wi-Fi' }, auth: { login: 'Masuk', @@ -38,14 +24,28 @@ const id = { illegalUsername: 'ada karakter ilegal pada nama user', illegalPassword: 'ada karakter ilegal pada sandi', forgetPassword: 'Lupa Sandi', - resetPassword: 'Atur ulang Sandi', - reset1: 'Jika lupa sandi, ikuti langkah berikut untuk mengatur ulang:', - reset2: '1. Masuk ke perangkat NanoKVM melalui SSH;', - reset3: '2. Hapus arsip di dalam perangkat: ', - reset4: '3. Gunakan akun awal untuk masuk: ', ok: 'Ok', cancel: 'Batalkan', - loginButtonText: 'Masuk' + loginButtonText: 'Masuk', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Mode Video', @@ -131,59 +131,113 @@ const id = { confirm: 'Ok' }, wol: { + title: 'Wake-on-LAN', sending: 'Kirim perintah...', sent: 'Perintah terkirim', input: 'Silahkan masukkan MAC', ok: 'Ok' }, - about: { - title: 'Tentang NanoKVM', - information: 'Informasi', - ip: 'IP', - mdns: 'mDNS', - application: 'Versi Aplikasi', - image: 'Version Gambar', - deviceKey: 'Kunci Perangkat', - queryFailed: 'Kueri gagal', - community: 'Komunitas' + power: { + title: 'Daya', + reset: 'Mulai Ulang', + power: 'Daya', + powerShort: 'Data (tekan sebentar)', + powerLong: 'Power (tekan lama)' }, - update: { - title: 'Periksa pembaruan', - queryFailed: 'Gagal mendapatkan versi', - updateFailed: 'Gagal memperbarui, tolong coba lagi.', - isLatest: 'Kamu sudah menggunakan versi terbaru.', - available: 'Ada pembaruan baru. apa kamu mau memperbarui?', - updating: 'Pembaruan dimulai. Silahkan tunggu...', - confirm: 'Konfirmasi', - cancel: 'Batalkan' - }, - virtualDevice: { - network: 'Jaringan Virtual', - disk: 'Disk Virtual' - }, - tailscale: { - loading: 'Memuat...', - notInstall: 'Tailscale tidak ditemukan! Silahkan pasang.', - install: 'Memasang', - installing: 'Memasangkan', - failed: 'Gagal memasangkan', - retry: 'Harap segarkan dan coba lagi. Atau coba instal secara manual', - download: 'Mengunduh', - package: 'paket instalasi', - unzip: 'dan unzip itu', - upTailscale: 'Unggah tailscale ke direktori NanoKVM /usr/bin/', - upTailscaled: 'Unggah tailscaled ke direktori NanoKVM /usr/sbin/', - refresh: 'Segarkan halaman ini', - notLogin: 'Perangkat belum ditautkan. Silakan masuk dan tautkan perangkat ini ke akun Anda.', - urlPeriod: 'Url ini berlaku selama 10 menit', - login: 'Masuk', - loginSuccess: 'Berhasil masuk', - enable: 'Aktifkan Tailscale', - deviceName: 'Nama Perangkat', - deviceIP: 'IP Perangkat', - account: 'Akun', - logout: 'Keluar', - logout2: 'Yakin untuk keluar?' + settings: { + title: 'Settings', + about: { + title: 'Tentang NanoKVM', + information: 'Informasi', + ip: 'IP', + mdns: 'mDNS', + application: 'Versi Aplikasi', + applicationTip: 'NanoKVM web application version', + image: 'Version Gambar', + imageTip: 'NanoKVM system image version', + deviceKey: 'Kunci Perangkat', + community: 'Komunitas' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Memuat...', + notInstall: 'Tailscale tidak ditemukan! Silahkan pasang.', + install: 'Memasang', + installing: 'Memasangkan', + failed: 'Gagal memasangkan', + retry: 'Harap segarkan dan coba lagi. Atau coba instal secara manual', + download: 'Mengunduh', + package: 'paket instalasi', + unzip: 'dan unzip itu', + upTailscale: 'Unggah tailscale ke direktori NanoKVM /usr/bin/', + upTailscaled: 'Unggah tailscaled ke direktori NanoKVM /usr/sbin/', + refresh: 'Segarkan halaman ini', + notLogin: + 'Perangkat belum ditautkan. Silakan masuk dan tautkan perangkat ini ke akun Anda.', + urlPeriod: 'Url ini berlaku selama 10 menit', + login: 'Masuk', + loginSuccess: 'Berhasil masuk', + enable: 'Aktifkan Tailscale', + deviceName: 'Nama Perangkat', + deviceIP: 'IP Perangkat', + account: 'Akun', + logout: 'Keluar', + logout2: 'Yakin untuk keluar?' + }, + update: { + title: 'Periksa pembaruan', + queryFailed: 'Gagal mendapatkan versi', + updateFailed: 'Gagal memperbarui, tolong coba lagi.', + isLatest: 'Kamu sudah menggunakan versi terbaru.', + available: 'Ada pembaruan baru. apa kamu mau memperbarui?', + updating: 'Pembaruan dimulai. Silahkan tunggu...', + confirm: 'Konfirmasi', + cancel: 'Batalkan' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/it.ts b/web/src/i18n/locales/it.ts index d0dad42..6a7a63c 100644 --- a/web/src/i18n/locales/it.ts +++ b/web/src/i18n/locales/it.ts @@ -1,25 +1,11 @@ const it = { translation: { - language: 'Lingua', - changePassword: 'Cambia Password', - logout: 'Disconnetti', - settings: 'Impostazioni', - showMouse: 'Mostra mouse', - hideMouse: 'Nascondi mouse', - power: 'Accensione', - reset: 'Reimposta', - powerShort: 'Accensione (clic breve)', - powerLong: 'Accensione (clic lungo)', - hddLed: 'LED HDD', - checkLibFailed: 'Impossibile verificare la libreria runtime, riprova', - updateLibFailed: 'Impossibile aggiornare la libreria runtime, riprova', - updatingLib: "Aggiornamento libreria runtime. Aggiorna la pagina dopo l'aggiornamento.", - checkForUpdate: 'Controlla Aggiornamenti', head: { desktop: 'Desktop Remoto', login: 'Accesso', changePassword: 'Cambia Password', - terminal: 'Terminale' + terminal: 'Terminale', + wifi: 'Wi-Fi' }, auth: { login: 'Accesso', @@ -39,14 +25,28 @@ const it = { illegalUsername: 'Il nome utente contiene caratteri non validi', illegalPassword: 'La password contiene caratteri non validi', forgetPassword: 'Hai dimenticato la password', - resetPassword: 'Reimposta Password', - reset1: 'Se hai dimenticato la password, segui questi passaggi per reimpostarla:', - reset2: '1. Accedi al dispositivo NanoKVM tramite SSH;', - reset3: '2. Elimina il file nel dispositivo: ', - reset4: "3. Usa l'account predefinito per accedere: ", ok: 'Ok', cancel: 'Annulla', - loginButtonText: 'Accedi' + loginButtonText: 'Accedi', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Modalità video', @@ -132,60 +132,113 @@ const it = { confirm: 'Ok' }, wol: { + title: 'Wake-on-LAN', sending: 'Invio comando...', sent: 'Comando inviato', input: 'Inserisci il MAC', ok: 'Ok' }, - about: { - title: 'Informazioni su NanoKVM', - information: 'Informazioni', - ip: 'IP', - mdns: 'mDNS', - application: 'Versione Applicazione', - image: 'Versione Immagine', - deviceKey: 'Chiave Dispositivo', - queryFailed: 'Query fallita', - community: 'Comunità' + power: { + title: 'Accensione', + reset: 'Reimposta', + power: 'Accensione', + powerShort: 'Accensione (clic breve)', + powerLong: 'Accensione (clic lungo)' }, - update: { - title: 'Controlla Aggiornamenti', - queryFailed: 'Impossibile ottenere la versione', - updateFailed: 'Aggiornamento fallito. Riprova.', - isLatest: 'Hai già la versione più recente.', - available: 'Un aggiornamento è disponibile. Sei sicuro di voler aggiornare?', - updating: 'Aggiornamento avviato. Attendere prego...', - confirm: 'Conferma', - cancel: 'Annulla' - }, - virtualDevice: { - network: 'Rete Virtuale', - usb: 'Disk Virtuale' - }, - tailscale: { - loading: 'Caricamento...', - notInstall: 'Tailscale non trovato! Per favore, installa.', - install: 'Installa', - installing: 'Installazione in corso', - failed: 'Installazione fallita', - retry: 'Riprova aggiornando la pagina o installa manualmente', - download: 'Scarica il', - package: 'pacchetto di installazione', - unzip: 'e decomprimilo', - upTailscale: 'Carica tailscale nella directory /usr/bin/ del NanoKVM', - upTailscaled: 'Carica tailscaled nella directory /usr/sbin/ del NanoKVM', - refresh: 'Aggiorna la pagina corrente', - notLogin: - 'Il dispositivo non è ancora stato associato. Effettua il login e associa questo dispositivo al tuo account.', - urlPeriod: 'Questo URL è valido per 10 minuti', - login: 'Accedi', - loginSuccess: 'Accesso riuscito', - enable: 'Abilita Tailscale', - deviceName: 'Nome Dispositivo', - deviceIP: 'IP Dispositivo', - account: 'Account', - logout: 'Disconnetti', - logout2: 'Sei sicuro di voler uscire?' + settings: { + title: 'Settings', + about: { + title: 'Informazioni su NanoKVM', + information: 'Informazioni', + ip: 'IP', + mdns: 'mDNS', + application: 'Versione Applicazione', + applicationTip: 'NanoKVM web application version', + image: 'Versione Immagine', + imageTip: 'NanoKVM system image version', + deviceKey: 'Chiave Dispositivo', + community: 'Comunità' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Caricamento...', + notInstall: 'Tailscale non trovato! Per favore, installa.', + install: 'Installa', + installing: 'Installazione in corso', + failed: 'Installazione fallita', + retry: 'Riprova aggiornando la pagina o installa manualmente', + download: 'Scarica il', + package: 'pacchetto di installazione', + unzip: 'e decomprimilo', + upTailscale: 'Carica tailscale nella directory /usr/bin/ del NanoKVM', + upTailscaled: 'Carica tailscaled nella directory /usr/sbin/ del NanoKVM', + refresh: 'Aggiorna la pagina corrente', + notLogin: + 'Il dispositivo non è ancora stato associato. Effettua il login e associa questo dispositivo al tuo account.', + urlPeriod: 'Questo URL è valido per 10 minuti', + login: 'Accedi', + loginSuccess: 'Accesso riuscito', + enable: 'Abilita Tailscale', + deviceName: 'Nome Dispositivo', + deviceIP: 'IP Dispositivo', + account: 'Account', + logout: 'Disconnetti', + logout2: 'Sei sicuro di voler uscire?' + }, + update: { + title: 'Controlla Aggiornamenti', + queryFailed: 'Impossibile ottenere la versione', + updateFailed: 'Aggiornamento fallito. Riprova.', + isLatest: 'Hai già la versione più recente.', + available: 'Un aggiornamento è disponibile. Sei sicuro di voler aggiornare?', + updating: 'Aggiornamento avviato. Attendere prego...', + confirm: 'Conferma', + cancel: 'Annulla' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/ja.ts b/web/src/i18n/locales/ja.ts index 9ad81a2..16bccf2 100644 --- a/web/src/i18n/locales/ja.ts +++ b/web/src/i18n/locales/ja.ts @@ -1,25 +1,11 @@ const ja = { translation: { - language: '言語', - changePassword: 'パスワード変更', - logout: 'ログアウト', - settings: '設定', - showMouse: 'マウスを表示', - hideMouse: 'マウスを非表示', - power: '電源', - reset: 'リセット', - powerShort: '電源(短いクリック)', - powerLong: '電源(長いクリック)', - hddLed: 'HDD LED', - checkLibFailed: 'ランタイムライブラリのチェックに失敗しました。再試行してください。', - updateLibFailed: 'ランタイムライブラリの更新に失敗しました。再試行してください。', - updatingLib: 'ランタイムライブラリを更新中です。更新後にページをリフレッシュしてください。', - checkForUpdate: 'アップデートの確認', head: { desktop: 'リモートデスクトップ', login: 'ログイン', changePassword: 'パスワード変更', - terminal: 'ターミナル' + terminal: 'ターミナル', + wifi: 'Wi-Fi' }, auth: { login: 'ログイン', @@ -39,14 +25,28 @@ const ja = { illegalUsername: 'ユーザー名に不正な文字が含まれています', illegalPassword: 'パスワードに不正な文字が含まれています', forgetPassword: 'パスワードを忘れた', - resetPassword: 'パスワードリセット', - reset1: 'パスワードを忘れた場合は、以下の手順に従ってリセットしてください:', - reset2: '1. SSHを介してNanoKVMデバイスにログインします。', - reset3: '2. デバイス内のファイルを削除します:', - reset4: '3. デフォルトのアカウントでログインします:', ok: 'OK', cancel: 'キャンセル', - loginButtonText: 'ログイン' + loginButtonText: 'ログイン', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'ビデオモード', @@ -132,60 +132,113 @@ const ja = { confirm: 'OK' }, wol: { + title: 'Wake-on-LAN', sending: 'コマンドを送信中...', sent: 'コマンドを送信しました', input: 'MACを入力してください', ok: 'OK' }, - about: { - title: 'NanoKVMについて', - information: '情報', - ip: 'IP', - mdns: 'mDNS', - application: 'アプリケーションバージョン', - image: 'イメージバージョン', - firmware: 'ファームウェアバージョン', - deviceKey: 'デバイスキー', - queryFailed: 'クエリに失敗しました', - community: 'コミュニティ' + power: { + title: '電源', + reset: 'リセット', + power: '電源', + powerShort: '電源(短いクリック)', + powerLong: '電源(長いクリック)' }, - update: { - title: 'アップデートの確認', - queryFailed: 'バージョンの取得に失敗しました', - updateFailed: '更新に失敗しました。再試行してください。', - isLatest: '最新のバージョンを既に持っています。', - available: 'アップデートが利用可能です。更新してもよろしいですか?', - updating: '更新を開始しました。お待ちください...', - confirm: '確認', - cancel: 'キャンセル' - }, - virtualDevice: { - network: '仮想ネットワーク', - disk: '仮想ディスク' - }, - tailscale: { - loading: '読み込み中...', - notInstall: 'Tailscaleが見つかりません!インストールしてください。', - install: 'インストール', - installing: 'インストール中', - failed: 'インストールに失敗しました', - retry: 'ページをリフレッシュして再試行してください。または手動でインストールしてください', - download: 'インストールパッケージをダウンロードして', - package: '解凍してください', - upTailscale: 'tailscaleをNanoKVMのディレクトリ/usr/bin/にアップロードしてください', - upTailscaled: 'tailscaledをNanoKVMのディレクトリ/usr/sbin/にアップロードしてください', - refresh: '現在のページをリフレッシュします', - notLogin: - 'デバイスはまだバインドされていません。ログインしてこのデバイスをアカウントにバインドしてください。', - urlPeriod: 'このURLは10分間有効です', - login: 'ログイン', - loginSuccess: 'ログイン成功', - enable: 'Tailscaleを有効化', - deviceName: 'デバイス名', - deviceIP: 'デバイスIP', - account: 'アカウント', - logout: 'ログアウト', - logout2: 'ログアウトしてもよろしいですか?' + settings: { + title: 'Settings', + about: { + title: 'NanoKVMについて', + information: '情報', + ip: 'IP', + mdns: 'mDNS', + application: 'アプリケーションバージョン', + applicationTip: 'NanoKVM web application version', + image: 'イメージバージョン', + imageTip: 'NanoKVM system image version', + firmware: 'ファームウェアバージョン', + deviceKey: 'デバイスキー', + community: 'コミュニティ' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: '読み込み中...', + notInstall: 'Tailscaleが見つかりません!インストールしてください。', + install: 'インストール', + installing: 'インストール中', + failed: 'インストールに失敗しました', + retry: 'ページをリフレッシュして再試行してください。または手動でインストールしてください', + download: 'インストールパッケージをダウンロードして', + package: '解凍してください', + upTailscale: 'tailscaleをNanoKVMのディレクトリ/usr/bin/にアップロードしてください', + upTailscaled: 'tailscaledをNanoKVMのディレクトリ/usr/sbin/にアップロードしてください', + refresh: '現在のページをリフレッシュします', + notLogin: + 'デバイスはまだバインドされていません。ログインしてこのデバイスをアカウントにバインドしてください。', + urlPeriod: 'このURLは10分間有効です', + login: 'ログイン', + loginSuccess: 'ログイン成功', + enable: 'Tailscaleを有効化', + deviceName: 'デバイス名', + deviceIP: 'デバイスIP', + account: 'アカウント', + logout: 'ログアウト', + logout2: 'ログアウトしてもよろしいですか?' + }, + update: { + title: 'アップデートの確認', + queryFailed: 'バージョンの取得に失敗しました', + updateFailed: '更新に失敗しました。再試行してください。', + isLatest: '最新のバージョンを既に持っています。', + available: 'アップデートが利用可能です。更新してもよろしいですか?', + updating: '更新を開始しました。お待ちください...', + confirm: '確認', + cancel: 'キャンセル' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/ko.ts b/web/src/i18n/locales/ko.ts index 3814fb9..9a106b8 100644 --- a/web/src/i18n/locales/ko.ts +++ b/web/src/i18n/locales/ko.ts @@ -1,25 +1,11 @@ const ko = { translation: { - language: '언어', - changePassword: '비밀번호 변경', - logout: '로그아웃', - settings: '설정', - showMouse: '마우스 보이기', - hideMouse: '마우스 숨기기', - power: '전원', - reset: '리셋', - powerShort: '전원 (짧게 누르기)', - powerLong: '전원 (길게 누르기)', - hddLed: 'HDD LED', - checkLibFailed: '런타임 라이브러리를 체크하는데 실패했습니다. 다시 시도해주세요.', - updateLibFailed: '런타임 라이브러리를 업데이트하는데 실패했습니다. 다시 시도해주세요.', - updatingLib: '런타임 라이브러리를 업데이트 하는 중입니다. 업데이트 완료 후 새로고침 해주세요.', - checkForUpdate: '업데이트 체크하기', head: { desktop: '원격 데스크톱', login: '로그인', changePassword: '비밀번호 변경', - terminal: '터미널' + terminal: '터미널', + wifi: 'Wi-Fi' }, auth: { login: '로그인', @@ -38,14 +24,28 @@ const ko = { illegalUsername: '유저 이름에 사용할 수 없는 문자가 있습니다.', illegalPassword: '비밀번호에 사용할 수 없는 문자가 있습니다.', forgetPassword: '비밀번호 분실', - resetPassword: '비밀번호 초기화', - reset1: '비밀번호를 분실하신 경우, 아래 순서로 리셋하세요.:', - reset2: '1. NanoKVM 기기에 SSH를 통해 로그인 합니다.;', - reset3: '2. 기기내 파일을 제거합니다.: ', - reset4: '3. 기본 계정으로 로그인 합니다.: ', ok: '확인', cancel: '취소', - loginButtonText: '로그인' + loginButtonText: '로그인', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: '비디오 모드', @@ -130,59 +130,112 @@ const ko = { confirm: '확인' }, wol: { + title: 'Wake-on-LAN', sending: '패킷 전송 중...', sent: '패킷 전송 완료', input: 'MAC주소를 입력하세요.', ok: '확인' }, - about: { - title: 'NanoKVM 정보', - information: '정보', - ip: 'IP', - mdns: 'mDNS', - application: '펌웨어 버전', - image: '이미지 버전', - deviceKey: '장치 키', - queryFailed: '불러오기 실패', - community: '커뮤니티' + power: { + title: '전원', + reset: '리셋', + power: '전원', + powerShort: '전원 (짧게 누르기)', + powerLong: '전원 (길게 누르기)' }, - update: { - title: '업데이트 확인 중', - queryFailed: '버전 확인 실패', - updateFailed: '업데이트 실패, 재시도하세요.', - isLatest: '이미 최신 버전입니다.', - available: '업데이트가 가능합니다. 정말로 업데이트 할까요?', - updating: '업데이트 시작. 잠시 기다려주세요...', - confirm: '확인', - cancel: '취소' - }, - virtualDevice: { - network: '가상 네트워크 카드', - disk: '가상 디스크' - }, - tailscale: { - loading: 'Loading...', - notInstall: 'Tailscale이 없습니다. 설치해주세요.', - install: '설치', - installing: '설치중', - failed: '설치 실패', - retry: '새로고침하고 다시 시도하거나, 수동으로 설치하세요', - download: '다운로드 중 :', - package: '패키지 설치', - unzip: '압축 해제', - upTailscale: 'tailscale을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/bin/', - upTailscaled: 'tailscaled을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/sbin/', - refresh: '현재 페이지 새로고침', - notLogin: '이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.', - urlPeriod: '이 주소는 10분간 유효합니다.', - login: '로그인', - loginSuccess: '로그인 성공', - enable: 'Tailscale 활성화', - deviceName: '장치 이름', - deviceIP: '장치 IP', - account: '계정', - logout: '로그아웃', - logout2: '정말로 로그아웃 합니까?' + settings: { + title: 'Settings', + about: { + title: 'NanoKVM 정보', + information: '정보', + ip: 'IP', + mdns: 'mDNS', + application: '펌웨어 버전', + applicationTip: 'NanoKVM web application version', + image: '이미지 버전', + imageTip: 'NanoKVM system image version', + deviceKey: '장치 키', + community: '커뮤니티' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Loading...', + notInstall: 'Tailscale이 없습니다. 설치해주세요.', + install: '설치', + installing: '설치중', + failed: '설치 실패', + retry: '새로고침하고 다시 시도하거나, 수동으로 설치하세요', + download: '다운로드 중 :', + package: '패키지 설치', + unzip: '압축 해제', + upTailscale: 'tailscale을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/bin/', + upTailscaled: 'tailscaled을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/sbin/', + refresh: '현재 페이지 새로고침', + notLogin: '이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.', + urlPeriod: '이 주소는 10분간 유효합니다.', + login: '로그인', + loginSuccess: '로그인 성공', + enable: 'Tailscale 활성화', + deviceName: '장치 이름', + deviceIP: '장치 IP', + account: '계정', + logout: '로그아웃', + logout2: '정말로 로그아웃 합니까?' + }, + update: { + title: '업데이트 확인 중', + queryFailed: '버전 확인 실패', + updateFailed: '업데이트 실패, 재시도하세요.', + isLatest: '이미 최신 버전입니다.', + available: '업데이트가 가능합니다. 정말로 업데이트 할까요?', + updating: '업데이트 시작. 잠시 기다려주세요...', + confirm: '확인', + cancel: '취소' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/nb.ts b/web/src/i18n/locales/nb.ts index 91b94a6..648601a 100644 --- a/web/src/i18n/locales/nb.ts +++ b/web/src/i18n/locales/nb.ts @@ -1,165 +1,202 @@ const nb = { - translation: { - language: 'Språk', + translation: { + head: { + desktop: 'Eksternt skrivebord', + login: 'Logg inn', changePassword: 'Endre passord', - logout: 'Logg ut', - settings: 'Innstillinger', - showMouse: 'Vis peker', - hideMouse: 'Skjul peker', - power: 'På-knapp', + terminal: 'Terminal', + wifi: 'Wi-Fi' + }, + auth: { + login: 'Logg inn', + placeholderUsername: 'Brukernavn', + placeholderPassword: 'Passord', + placeholderPassword2: 'Oppgi passord igjen', + noEmptyUsername: 'Brukernavn påkrevd', + noEmptyPassword: 'Passord påkrevd', + noAccount: + 'Kunne ikke hente brukerinformasjon. Vennligst last inn siden på nytt eller gjenopprett passord', + invalidUser: 'Ugyldig brukernavn eller passord', + error: 'Uventet feil', + changePassword: 'Endre passord', + changePasswordDesc: + 'For sikkerheten til enheten, vennligst endre passordet ditt for web-innlogging.', + differentPassword: 'Passordene er ikke like', + illegalUsername: 'Brukernavn inneholder tegn som ikke er tillat', + illegalPassword: 'Passord inneholder tegn som ikke er tillat', + forgetPassword: 'Glemt passord', + ok: 'Ok', + cancel: 'Avbryt', + loginButtonText: 'Logg inn', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' + }, + screen: { + video: 'Video-kodek', + resolution: 'Oppløsning', + auto: 'Automatisk', + autoTips: + 'Skjermriving eller peker-forskyvning kan oppstå ved enkelte oppløsninger. Prøv å justere den eksterne vertens oppløsning eller skru av automatisk modus.', + fps: 'FPS', + customizeFps: 'Tilpass', + quality: 'Kvalitet', + qualityLossless: 'Tapsfri', + qualityHigh: 'Høy', + qualityMedium: 'Medium', + qualityLow: 'Lav', + frameDetect: 'Bildefrekvensoppdagelse', + frameDetectTip: + 'Kalkuler forskjellen mellom bilder. Stopper overføring av video når det ikke oppdages forskjell på den eksterne vertens skjerm.' + }, + keyboard: { + paste: 'Lim inn', + tips: 'Kun vanlige tegn på tastatur er støttet', + placeholder: 'Vennligst angi teksten du vil lime inn', + submit: 'Lim inn', + virtual: 'Åpne tastatur' + }, + mouse: { + default: 'Vanlig', + pointer: 'Hånd', + cell: 'Celle', + text: 'Tekst', + grab: 'Grip', + hide: 'Skjul', + mode: 'Modus', + absolute: 'Absolutt', + relative: 'Relativ', + requestPointer: 'Bruker relativ modus. Vennligsk klikk på skrivebordet for vise musepeker.', + resetHid: 'Gjenopprett HID' + }, + image: { + title: 'Bilder', + loading: 'Laster...', + empty: 'Ingen funnet', + mountFailed: 'Montering feilet', + mountDesc: + 'På noen systemer er det nødvendig å koble fra den virtuelle disken på den eksterne verten før man kan montere arkivfilen.', + tips: { + title: 'Hvordan laste opp', + usb1: 'Koble til NanoKVM-enheten til din datamaskin med USB.', + usb2: 'Sikre at den virtuelle disken er montert (Innstillinger - Virtuell disk).', + usb3: 'Åpne den virtuelle disken på datamaskinen din og kopier arkivfilen til rot-mappen på den virtuelle disken.', + scp1: 'Sikre at NanoKVM-enheten og datamaskinen din er tilkoblet det samme lokale nettverket.', + scp2: 'Åpne en terminal på datamaskinen din og bruk SCP-kommandoen til å laste opp arkivfilen til mappen /data på NanoKVM-enheten.', + scp3: 'Eksempel: scp sti-til-din-arkivfil root@din-nanokvm-ip:/data', + tfCard: 'TF-kort', + tf1: 'Denne metoden er støttet på datamskiner med Linux', + tf2: 'Ta TF-kortet ut av NanoKVM-enheten (hvis du har FULL-versjonen, demonter kabinettet først).', + tf3: 'Sett inn TF-kortet i en kortleser og koble den til datamaskinen din.', + tf4: 'Kopiér arkivfilen til mappen /data på TF-kortet.', + tf5: 'Sett inn TF-kortet i NanoKVM-enheten.' + } + }, + script: { + title: 'Skript', + upload: 'Last opp', + run: 'Kjør', + runBackground: 'Kjør i bakgrunnen', + runFailed: 'Kjøring feilet', + attention: 'Merknad', + delDesc: 'Er du sikker på at du vil slette denne filen?', + confirm: 'Ja', + cancel: 'Nei', + delete: 'Slett', + close: 'Lukk' + }, + terminal: { + title: 'Terminal', + nanokvm: 'NanoKVM', + serial: 'Seriell port', + serialPort: 'Seriell port', + serialPortPlaceholder: 'Vennligst angi den serielle porten', + baudrate: 'Baud-rate', + confirm: 'Ok' + }, + wol: { + title: 'Wake-on-LAN', + sending: 'Sender kommando...', + sent: 'Kommando sendt', + input: 'Vennligst angi MAC-adressen', + ok: 'Ok' + }, + power: { + title: 'På-knapp', reset: 'Reset-knapp', + power: 'På-knapp', powerShort: 'På-knapp (kort trykk)', - powerLong: 'På-knapp (langt trykk)', - hddLed: 'Disk-aktivitet', - checkLibFailed: 'Kunne ikke sjekke runtime-bibliotek. Prøv igjen.', - updateLibFailed: 'Kunne ikke oppdatere runtime-bibliotek. Prøv igjen.', - updatingLib: 'Oppdaterer runtime-bibliotek. Vennligst last inn siden på nytt etter oppdatering.', - checkForUpdate: 'Se etter oppdatering', - head: { - desktop: 'Eksternt skrivebord', - login: 'Logg inn', - changePassword: 'Endre passord', - terminal: 'Terminal' - }, - auth: { - login: 'Logg inn', - placeholderUsername: 'Brukernavn', - placeholderPassword: 'Passord', - placeholderPassword2: 'Oppgi passord igjen', - noEmptyUsername: 'Brukernavn påkrevd', - noEmptyPassword: 'Passord påkrevd', - noAccount: 'Kunne ikke hente brukerinformasjon. Vennligst last inn siden på nytt eller gjenopprett passord', - invalidUser: 'Ugyldig brukernavn eller passord', - error: 'Uventet feil', - changePassword: 'Endre passord', - changePasswordDesc: 'For sikkerheten til enheten, vennligst endre passordet ditt for web-innlogging.', - differentPassword: 'Passordene er ikke like', - illegalUsername: 'Brukernavn inneholder tegn som ikke er tillat', - illegalPassword: 'Passord inneholder tegn som ikke er tillat', - forgetPassword: 'Glemt passord', - resetPassword: 'Gjenopprett passord', - reset1: 'Hvis du har glemt passordet ditt, vennligst følg disse trinnene for å gjenopprette det:', - reset2: '1. Logg inn på NanoKVM-enheten med SSH', - reset3: '2. Slett filen på enheten: ', - reset4: '3. Bruk standardkontoen til å logge inn: ', - ok: 'Ok', - cancel: 'Avbryt', - loginButtonText: 'Logg inn', - }, - screen: { - video: 'Video-kodek', - resolution: 'Oppløsning', - auto: 'Automatisk', - autoTips: - "Skjermriving eller peker-forskyvning kan oppstå ved enkelte oppløsninger. Prøv å justere den eksterne vertens oppløsning eller skru av automatisk modus.", - fps: 'FPS', - customizeFps: 'Tilpass', - quality: 'Kvalitet', - qualityLossless: 'Tapsfri', - qualityHigh: 'Høy', - qualityMedium: 'Medium', - qualityLow: 'Lav', - frameDetect: 'Bildefrekvensoppdagelse', - frameDetectTip: - "Kalkuler forskjellen mellom bilder. Stopper overføring av video når det ikke oppdages forskjell på den eksterne vertens skjerm." - }, - keyboard: { - paste: 'Lim inn', - tips: 'Kun vanlige tegn på tastatur er støttet', - placeholder: 'Vennligst angi teksten du vil lime inn', - submit: 'Lim inn', - virtual: 'Åpne tastatur' - }, - mouse: { - default: 'Vanlig', - pointer: 'Hånd', - cell: 'Celle', - text: 'Tekst', - grab: 'Grip', - hide: 'Skjul', - mode: 'Modus', - absolute: 'Absolutt', - relative: 'Relativ', - requestPointer: 'Bruker relativ modus. Vennligsk klikk på skrivebordet for vise musepeker.', - resetHid: 'Gjenopprett HID' - }, - image: { - title: 'Bilder', - loading: 'Laster...', - empty: 'Ingen funnet', - mountFailed: 'Montering feilet', - mountDesc: - "På noen systemer er det nødvendig å koble fra den virtuelle disken på den eksterne verten før man kan montere arkivfilen.", - tips: { - title: 'Hvordan laste opp', - usb1: 'Koble til NanoKVM-enheten til din datamaskin med USB.', - usb2: 'Sikre at den virtuelle disken er montert (Innstillinger - Virtuell disk).', - usb3: 'Åpne den virtuelle disken på datamaskinen din og kopier arkivfilen til rot-mappen på den virtuelle disken.', - scp1: 'Sikre at NanoKVM-enheten og datamaskinen din er tilkoblet det samme lokale nettverket.', - scp2: 'Åpne en terminal på datamaskinen din og bruk SCP-kommandoen til å laste opp arkivfilen til mappen /data på NanoKVM-enheten.', - scp3: 'Eksempel: scp sti-til-din-arkivfil root@din-nanokvm-ip:/data', - tfCard: 'TF-kort', - tf1: 'Denne metoden er støttet på datamskiner med Linux', - tf2: 'Ta TF-kortet ut av NanoKVM-enheten (hvis du har FULL-versjonen, demonter kabinettet først).', - tf3: 'Sett inn TF-kortet i en kortleser og koble den til datamaskinen din.', - tf4: 'Kopiér arkivfilen til mappen /data på TF-kortet.', - tf5: 'Sett inn TF-kortet i NanoKVM-enheten.' - } - }, - script: { - title: 'Skript', - upload: 'Last opp', - run: 'Kjør', - runBackground: 'Kjør i bakgrunnen', - runFailed: 'Kjøring feilet', - attention: 'Merknad', - delDesc: 'Er du sikker på at du vil slette denne filen?', - confirm: 'Ja', - cancel: 'Nei', - delete: 'Slett', - close: 'Lukk' - }, - terminal: { - title: 'Terminal', - nanokvm: 'NanoKVM', - serial: 'Seriell port', - serialPort: 'Seriell port', - serialPortPlaceholder: 'Vennligst angi den serielle porten', - baudrate: 'Baud-rate', - confirm: 'Ok' - }, - wol: { - sending: 'Sender kommando...', - sent: 'Kommando sendt', - input: 'Vennligst angi MAC-adressen', - ok: 'Ok' - }, + powerLong: 'På-knapp (langt trykk)' + }, + settings: { + title: 'Settings', about: { title: 'Om NanoKVM', information: 'Informasjon', ip: 'IP', mdns: 'mDNS', application: 'Applikasjonsversjon', + applicationTip: 'NanoKVM web application version', image: 'Arkivfil-versjon', + imageTip: 'NanoKVM system image version', deviceKey: 'Enhetsnøkkel', - queryFailed: 'Spørring feilet', community: 'Fellesskap' }, - update: { - title: 'Se etter oppdatering', - queryFailed: 'Kunne ikke hente versjon', - updateFailed: 'En feil oppstod under oppdatering. Vennligst forsøk igjen.', - isLatest: 'Du har siste versjon allerede.', - available: 'En oppdatering er tilgjengelig. Er du sikker på at du ønsker å oppdatere?', - updating: 'Oppdatering har startet. Vennligst vent...', - confirm: 'Oppdater', - cancel: 'Avbryt' + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' }, - virtualDevice: { - network: 'Virtuelt nettverk', - disk: 'Virtuell disk' + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } }, tailscale: { + title: 'Tailscale', loading: 'Laster...', notInstall: 'Tailscale er ikke funnet! Vennligst installer.', install: 'Installér', @@ -183,8 +220,26 @@ const nb = { account: 'Konto', logout: 'Logg ut', logout2: 'Er du sikker på at du ønsker å logge ut?' + }, + update: { + title: 'Se etter oppdatering', + queryFailed: 'Kunne ikke hente versjon', + updateFailed: 'En feil oppstod under oppdatering. Vennligst forsøk igjen.', + isLatest: 'Du har siste versjon allerede.', + available: 'En oppdatering er tilgjengelig. Er du sikker på at du ønsker å oppdatere?', + updating: 'Oppdatering har startet. Vennligst vent...', + confirm: 'Oppdater', + cancel: 'Avbryt' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' } } - }; + } +}; - export default nb; +export default nb; diff --git a/web/src/i18n/locales/nl.ts b/web/src/i18n/locales/nl.ts index 6eec402..60e7d3b 100644 --- a/web/src/i18n/locales/nl.ts +++ b/web/src/i18n/locales/nl.ts @@ -1,25 +1,11 @@ const nl = { translation: { - language: 'Taal', - changePassword: 'Wachtwoord wijzigen', - logout: 'Uitloggen', - settings: 'Instellingen', - showMouse: 'Muis tonen', - hideMouse: 'Muis verbergen', - power: 'Aan/uit', - reset: 'Resetten', - powerShort: 'Aan/uit (kort indrukken)', - powerLong: 'Aan/uit (lang indrukken)', - hddLed: 'HDD LED', - checkLibFailed: 'Controleren van runtime bibliotheek mislukt, probeer het opnieuw', - updateLibFailed: 'Bijwerken van runtime bibliotheek mislukt, probeer het opnieuw', - updatingLib: 'Runtime bibliotheek wordt bijgewerkt. Vernieuw de pagina na het bijwerken.', - checkForUpdate: 'Controleren op updates', head: { desktop: 'Extern bureaublad', login: 'Inloggen', changePassword: 'Wachtwoord wijzigen', - terminal: 'Terminal' + terminal: 'Terminal', + wifi: 'Wi-Fi' }, auth: { login: 'Inloggen', @@ -39,14 +25,28 @@ const nl = { illegalUsername: 'Gebruikersnaam bevat ongeldige tekens', illegalPassword: 'Wachtwoord bevat ongeldige tekens', forgetPassword: 'Wachtwoord vergeten', - resetPassword: 'Wachtwoord resetten', - reset1: 'Als u uw wachtwoord bent vergeten, volg dan deze stappen om het te resetten:', - reset2: '1. Log in op het NanoKVM-apparaat via SSH;', - reset3: '2. Verwijder het bestand in het apparaat: ', - reset4: '3. Gebruik het standaardaccount om in te loggen: ', ok: 'Ok', cancel: 'Annuleren', - loginButtonText: 'Inloggen' + loginButtonText: 'Inloggen', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Videomodus', @@ -132,59 +132,113 @@ const nl = { confirm: 'Ok' }, wol: { + title: 'Wake-on-LAN', sending: 'Commando wordt verzonden...', sent: 'Commando verzonden', input: 'Voer het MAC-adres in', ok: 'Ok' }, - about: { - title: 'Over NanoKVM', - information: 'Informatie', - ip: 'IP', - mdns: 'mDNS', - application: 'Applicatie versie', - image: 'Image versie', - deviceKey: 'Apparaat sleutel', - queryFailed: 'Opvragen mislukt', - community: 'Community' + power: { + title: 'Aan/uit', + reset: 'Resetten', + power: 'Aan/uit', + powerShort: 'Aan/uit (kort indrukken)', + powerLong: 'Aan/uit (lang indrukken)' }, - update: { - title: 'Controleren op updates', - queryFailed: 'Ophalen versie mislukt', - updateFailed: 'Update mislukt. Probeer het opnieuw.', - isLatest: 'U heeft al de nieuwste versie.', - available: 'Er is een update beschikbaar. Weet u zeker dat u wilt updaten?', - updating: 'Update gestart. Even geduld a.u.b...', - confirm: 'Bevestigen', - cancel: 'Annuleren' - }, - virtualDevice: { - network: 'Virtueel netwerk', - disk: 'Virtuele schijf' - }, - tailscale: { - loading: 'Laden...', - notInstall: 'Tailscale niet gevonden! Installeer a.u.b.', - install: 'Installeren', - installing: 'Installeren', - failed: 'Installatie mislukt', - retry: 'Vernieuw en probeer opnieuw. Of probeer handmatig te installeren', - download: 'Download het', - package: 'installatiepakket', - unzip: 'en pak het uit', - upTailscale: 'Upload tailscale naar NanoKVM directory /usr/bin/', - upTailscaled: 'Upload tailscaled naar NanoKVM directory /usr/sbin/', - refresh: 'Vernieuw huidige pagina', - notLogin: 'Het apparaat is nog niet gekoppeld. Log in en koppel dit apparaat aan uw account.', - urlPeriod: 'Deze url is 10 minuten geldig', - login: 'Inloggen', - loginSuccess: 'Inloggen gelukt', - enable: 'Tailscale inschakelen', - deviceName: 'Apparaatnaam', - deviceIP: 'Apparaat IP', - account: 'Account', - logout: 'Uitloggen', - logout2: 'Weet u zeker dat u wilt uitloggen?' + settings: { + title: 'Settings', + about: { + title: 'Over NanoKVM', + information: 'Informatie', + ip: 'IP', + mdns: 'mDNS', + application: 'Applicatie versie', + applicationTip: 'NanoKVM web application version', + image: 'Image versie', + imageTip: 'NanoKVM system image version', + deviceKey: 'Apparaat sleutel', + community: 'Community' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Laden...', + notInstall: 'Tailscale niet gevonden! Installeer a.u.b.', + install: 'Installeren', + installing: 'Installeren', + failed: 'Installatie mislukt', + retry: 'Vernieuw en probeer opnieuw. Of probeer handmatig te installeren', + download: 'Download het', + package: 'installatiepakket', + unzip: 'en pak het uit', + upTailscale: 'Upload tailscale naar NanoKVM directory /usr/bin/', + upTailscaled: 'Upload tailscaled naar NanoKVM directory /usr/sbin/', + refresh: 'Vernieuw huidige pagina', + notLogin: + 'Het apparaat is nog niet gekoppeld. Log in en koppel dit apparaat aan uw account.', + urlPeriod: 'Deze url is 10 minuten geldig', + login: 'Inloggen', + loginSuccess: 'Inloggen gelukt', + enable: 'Tailscale inschakelen', + deviceName: 'Apparaatnaam', + deviceIP: 'Apparaat IP', + account: 'Account', + logout: 'Uitloggen', + logout2: 'Weet u zeker dat u wilt uitloggen?' + }, + update: { + title: 'Controleren op updates', + queryFailed: 'Ophalen versie mislukt', + updateFailed: 'Update mislukt. Probeer het opnieuw.', + isLatest: 'U heeft al de nieuwste versie.', + available: 'Er is een update beschikbaar. Weet u zeker dat u wilt updaten?', + updating: 'Update gestart. Even geduld a.u.b...', + confirm: 'Bevestigen', + cancel: 'Annuleren' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/pl.ts b/web/src/i18n/locales/pl.ts index d20a39c..add8dde 100644 --- a/web/src/i18n/locales/pl.ts +++ b/web/src/i18n/locales/pl.ts @@ -1,25 +1,11 @@ const pl = { translation: { - language: 'Język', - changePassword: 'Zmień hasło', - logout: 'Wyloguj', - settings: 'Ustawienia', - showMouse: 'Pokaż myszkę', - hideMouse: 'Ukryj myszkę', - power: 'Zasilanie', - reset: 'Reset', - powerShort: 'Zasilanie (krótkie kliknięcie)', - powerLong: 'Zasilanie (długie kliknięcie)', - hddLed: 'HDD LED', - checkLibFailed: 'Nie udało się sprawdzić biblioteki uruchomieniowej, spróbuj ponownie', - updateLibFailed: 'Nie udało się zaktualizować biblioteki uruchomieniowej, spróbuj ponownie', - updatingLib: 'Aktualizowanie biblioteki uruchomieniowej. Odśwież stronę po aktualizacji.', - checkForUpdate: 'Aktualizacja systemu', head: { desktop: 'Zdalny pulpit', login: 'Login', changePassword: 'Zmień Hasło', - terminal: 'Terminal' + terminal: 'Terminal', + wifi: 'Wi-Fi' }, auth: { login: 'Login', @@ -39,14 +25,20 @@ const pl = { illegalUsername: 'nazwa użytkownika zawiera niedozwolone znaki', illegalPassword: 'hasło zawiera niedozwolone znaki', forgetPassword: 'Zapomiałeś hasła?', - resetPassword: 'Zmień hasło', - reset1: 'Jeśli zapomniałeś hasła, postępuj zgodnie z instrukcjami, aby je zresetować:', - reset2: '1. Zaloguj się do urządzenia NanoKVM przez SSH:', - reset3: '2. Usuń ten plik z urządzenia: ', - reset4: '3. Użyj domyślnego login aby zalogować: ', ok: 'Ok', cancel: 'Anuluj', - loginButtonText: 'Zaloguj się' + loginButtonText: 'Zaloguj się', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } }, screen: { video: 'Tryb wideo', @@ -131,60 +123,113 @@ const pl = { confirm: 'Ok' }, wol: { + title: 'Wake-on-LAN', sending: 'Wysyłanie komendy...', sent: 'Komenda wysłana', input: 'Wprowadź numer adresu MAC', ok: 'Ok' }, - about: { - title: 'NanoKVM - informacje', - information: 'Informacje o systemie', - ip: 'IP', - mdns: 'mDNS', - application: 'Wersja oprogramowania', - image: 'Wersja obrazu', - deviceKey: 'Klucz urządzenia', - queryFailed: 'Zapytanie nie powiodło się', - community: 'Społeczność' + power: { + title: 'Zasilanie', + reset: 'Reset', + power: 'Zasilanie', + powerShort: 'Zasilanie (krótkie kliknięcie)', + powerLong: 'Zasilanie (długie kliknięcie)' }, - update: { - title: 'Sprawdź aktualizacje', - queryFailed: 'Uzyskanie wersji nie powiodło się', - updateFailed: 'Aktualizacja nie powiodła się. Spróbuj ponownie.', - isLatest: 'Oprogramowanie jest aktualne.', - available: 'Aktualizacja jest dostępna. Czy na pewno chcesz dokonać aktualizacji?', - updating: 'Aktualizacja rozpoczęta. Proszę czekać...', - confirm: 'Potwierdź', - cancel: 'Anuluj' - }, - virtualDevice: { - network: 'Sieć wirtualna', - usb: 'Dysk wirtualny' - }, - tailscale: { - loading: 'Ładowanie...', - notInstall: 'Nie znaleziono Tailscale! Proszę zainstalować.', - install: 'Instaluj', - installing: 'Instalowanie', - failed: 'Instalowanie nie powiodło się', - retry: 'Odśwież stronę i spróbuj ponownie, albo spróbuj zainstalować manualnie.', - download: 'Pobierz', - package: 'pakiet instalacyjny', - unzip: 'i wypakuj pliki', - upTailscale: 'Prześlij tailscale do NanoKVM w katalogu /usr/bin/', - upTailscaled: 'Prześlij tailscaled do NanoKVM w katalogu /usr/sbin/', - refresh: 'Odśwież obecną stronę', - notLogin: - 'Urządzenie nie zostało jeszcze powiązane. Zaloguj się i powiąż to urządzenie ze swoim kontem.', - urlPeriod: 'Ten URL jest ważny przez 10 minut', - login: 'Zaloguj', - loginSuccess: 'Zalogowanie pomyślne', - enable: 'Włącz Tailscale', - deviceName: 'Nazwa urządzenia', - deviceIP: 'Adres IP urządzenia', - account: 'Konto', - logout: 'Wyloguj', - logout2: 'Chcesz się wylogować?' + settings: { + title: 'Settings', + about: { + title: 'NanoKVM - informacje', + information: 'Informacje o systemie', + ip: 'IP', + mdns: 'mDNS', + application: 'Wersja oprogramowania', + applicationTip: 'NanoKVM web application version', + image: 'Wersja obrazu', + imageTip: 'NanoKVM system image version', + deviceKey: 'Klucz urządzenia', + community: 'Społeczność' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Ładowanie...', + notInstall: 'Nie znaleziono Tailscale! Proszę zainstalować.', + install: 'Instaluj', + installing: 'Instalowanie', + failed: 'Instalowanie nie powiodło się', + retry: 'Odśwież stronę i spróbuj ponownie, albo spróbuj zainstalować manualnie.', + download: 'Pobierz', + package: 'pakiet instalacyjny', + unzip: 'i wypakuj pliki', + upTailscale: 'Prześlij tailscale do NanoKVM w katalogu /usr/bin/', + upTailscaled: 'Prześlij tailscaled do NanoKVM w katalogu /usr/sbin/', + refresh: 'Odśwież obecną stronę', + notLogin: + 'Urządzenie nie zostało jeszcze powiązane. Zaloguj się i powiąż to urządzenie ze swoim kontem.', + urlPeriod: 'Ten URL jest ważny przez 10 minut', + login: 'Zaloguj', + loginSuccess: 'Zalogowanie pomyślne', + enable: 'Włącz Tailscale', + deviceName: 'Nazwa urządzenia', + deviceIP: 'Adres IP urządzenia', + account: 'Konto', + logout: 'Wyloguj', + logout2: 'Chcesz się wylogować?' + }, + update: { + title: 'Sprawdź aktualizacje', + queryFailed: 'Uzyskanie wersji nie powiodło się', + updateFailed: 'Aktualizacja nie powiodła się. Spróbuj ponownie.', + isLatest: 'Oprogramowanie jest aktualne.', + available: 'Aktualizacja jest dostępna. Czy na pewno chcesz dokonać aktualizacji?', + updating: 'Aktualizacja rozpoczęta. Proszę czekać...', + confirm: 'Potwierdź', + cancel: 'Anuluj' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/ru.ts b/web/src/i18n/locales/ru.ts index 523ff7c..845f8d7 100644 --- a/web/src/i18n/locales/ru.ts +++ b/web/src/i18n/locales/ru.ts @@ -1,29 +1,11 @@ const ru = { translation: { - language: 'Язык', - changePassword: 'Изменить пароль', - logout: 'Выйти', - images: 'Образы', - loading: 'Загрузка', - empty: 'Пусто', - settings: 'Настройки', - showMouse: 'Показать мышь', - hideMouse: 'Скрыть мышь', - power: 'Питание', - reset: 'Экстренная перезагрузка', - powerShort: 'Питание (короткое нажатие)', - powerLong: 'Питание (длинное нажатие)', - hddLed: 'Индикатор активности хранилища', - checkLibFailed: 'Не удалось проверить библиотеку среды выполнения, попробуйте еще раз', - updateLibFailed: 'Не удалось обновить библиотеку среды выполнения, попробуйте еще раз', - updatingLib: - 'Обновление библиотеки среды выполнения. Пожалуйста, обновите страницу после обновления.', - checkForUpdate: 'Проверить обновления', head: { desktop: 'Удаленный рабочий стол', login: 'Войти', changePassword: 'Изменить пароль', - terminal: 'Терминал' + terminal: 'Терминал', + wifi: 'Wi-Fi' }, auth: { login: 'Войти', @@ -43,14 +25,28 @@ const ru = { illegalUsername: 'Имя пользователя содержит недопустимые символы', illegalPassword: 'Пароль содержит недопустимые символы', forgetPassword: 'Забыли пароль?', - resetPassword: 'Сбросить пароль', - reset1: 'Если вы забыли пароль, выполните следующие действия для его восстановления:', - reset2: '1. Войдите в устройство NanoKVM по протоколу SSH;', - reset3: '2. Удалите файл на устройстве: ', - reset4: '3. Используйте учетную запись по умолчанию для входа в систему: ', ok: 'ОК', cancel: 'Отмена', - loginButtonText: 'Войти' + loginButtonText: 'Войти', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Видеорежим', @@ -135,59 +131,112 @@ const ru = { confirm: 'ОК' }, wol: { + title: 'Wake-on-LAN', sending: 'Отправка команды...', sent: 'Команда отправлена', input: 'Введите MAC-адрес', ok: 'ОК' }, - about: { - title: 'О системе NanoKVM', - information: 'Информация', - ip: 'IP-адрес', - mdns: 'Доменное имя mDNS', - application: 'Версия ПО', - image: 'Версия прошивки', - deviceKey: 'Ключ устройства', - queryFailed: 'Запрос не удался', - community: 'Сообщество' + power: { + title: 'Питание', + reset: 'Экстренная перезагрузка', + power: 'Питание', + powerShort: 'Питание (короткое нажатие)', + powerLong: 'Питание (длинное нажатие)' }, - update: { - title: 'Проверить обновления', - queryFailed: 'Получить версию не удалось', - updateFailed: 'Обновление не удалось. Пожалуйста, попробуйте еще раз.', - isLatest: 'У вас уже есть последняя версия.', - available: 'Доступно обновление. Вы уверены, что хотите обновить?', - updating: 'Начато обновление. Пожалуйста, подождите...', - confirm: 'Подтвердить', - cancel: 'Отмена' - }, - virtualDevice: { - network: 'виртуальная сетевая карта', - disk: 'виртуальный диск' - }, - tailscale: { - loading: 'Загрузка...', - notInstall: 'Tailscale не найден! Пожалуйста, установите.', - install: 'Установить', - installing: 'Установка', - failed: 'Не удалось установить', - retry: 'Пожалуйста, обновите и попробуйте снова, или попробуйте установить вручную', - download: 'Скачайте', - package: 'установочный пакет', - unzip: 'и разархивируйте его', - upTailscale: 'Переместите tailscale в каталог /usr/bin/ на NanoKVM', - upTailscaled: 'Переместите tailscaled в каталог /usr/sbin/ на NanoKVM', - refresh: 'Обновите текущую страницу', - notLogin: 'Устройство не привязано. Войдите, чтобы привязать его к аккаунту.', - urlPeriod: 'Этот адрес действителен в течение 10 минут', - login: 'Войти', - loginSuccess: 'Вход выполнен', - enable: 'Включить Tailscale', - deviceName: 'Имя устройства', - deviceIP: 'IP адрес устройства', - account: 'аккаунт', - logout: 'Выход', - logout2: 'Вы действительно хотите выйти?' + settings: { + title: 'Settings', + about: { + title: 'О системе NanoKVM', + information: 'Информация', + ip: 'IP-адрес', + mdns: 'Доменное имя mDNS', + application: 'Версия ПО', + applicationTip: 'NanoKVM web application version', + image: 'Версия прошивки', + imageTip: 'NanoKVM system image version', + deviceKey: 'Ключ устройства', + community: 'Сообщество' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Загрузка...', + notInstall: 'Tailscale не найден! Пожалуйста, установите.', + install: 'Установить', + installing: 'Установка', + failed: 'Не удалось установить', + retry: 'Пожалуйста, обновите и попробуйте снова, или попробуйте установить вручную', + download: 'Скачайте', + package: 'установочный пакет', + unzip: 'и разархивируйте его', + upTailscale: 'Переместите tailscale в каталог /usr/bin/ на NanoKVM', + upTailscaled: 'Переместите tailscaled в каталог /usr/sbin/ на NanoKVM', + refresh: 'Обновите текущую страницу', + notLogin: 'Устройство не привязано. Войдите, чтобы привязать его к аккаунту.', + urlPeriod: 'Этот адрес действителен в течение 10 минут', + login: 'Войти', + loginSuccess: 'Вход выполнен', + enable: 'Включить Tailscale', + deviceName: 'Имя устройства', + deviceIP: 'IP адрес устройства', + account: 'аккаунт', + logout: 'Выход', + logout2: 'Вы действительно хотите выйти?' + }, + update: { + title: 'Проверить обновления', + queryFailed: 'Получить версию не удалось', + updateFailed: 'Обновление не удалось. Пожалуйста, попробуйте еще раз.', + isLatest: 'У вас уже есть последняя версия.', + available: 'Доступно обновление. Вы уверены, что хотите обновить?', + updating: 'Начато обновление. Пожалуйста, подождите...', + confirm: 'Подтвердить', + cancel: 'Отмена' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/uk.ts b/web/src/i18n/locales/uk.ts index 7f66aa1..821a2f6 100644 --- a/web/src/i18n/locales/uk.ts +++ b/web/src/i18n/locales/uk.ts @@ -1,25 +1,11 @@ const uk = { translation: { - language: 'Мова', - changePassword: 'Змінити пароль', - logout: 'Вийти', - settings: 'Налаштування', - showMouse: 'Показати мишу', - hideMouse: 'Сховати мишу', - power: 'Живлення', - reset: 'Скидання', - powerShort: 'Живлення (коротке натискання)', - powerLong: 'Живлення (довге натискання)', - hddLed: 'Індикатор HDD', - checkLibFailed: 'Не вдалося перевірити бібліотеку виконання, спробуйте ще раз', - updateLibFailed: 'Не вдалося оновити бібліотеку виконання, спробуйте ще раз', - updatingLib: 'Оновлення бібліотеки виконано. Оновіть сторінку після оновлення.', - checkForUpdate: 'Перевірити оновлення', head: { desktop: 'Віддалений робочий стіл', login: 'Вхід', changePassword: 'Змінити пароль', - terminal: 'Термінал' + terminal: 'Термінал', + wifi: 'Wi-Fi' }, auth: { login: 'Вхід', @@ -39,14 +25,28 @@ const uk = { illegalUsername: "ім'я користувача містить недопустимі символи", illegalPassword: 'пароль містить недопустимі символи', forgetPassword: 'Забули пароль', - resetPassword: 'Скинути пароль', - reset1: 'Якщо ви забули пароль, виконайте наступні кроки для його скидання:', - reset2: '1. Увійдіть на пристрій NanoKVM через SSH;', - reset3: '2. Видаліть файл на пристрої: ', - reset4: '3. Використовуйте обліковий запис за замовчуванням для входу: ', ok: 'Ок', cancel: 'Скасувати', - loginButtonText: 'Увійти' + loginButtonText: 'Увійти', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Відеорежим', @@ -132,60 +132,113 @@ const uk = { confirm: 'Ок' }, wol: { + title: 'Wake-on-LAN', sending: 'Посилання команди...', sent: 'Команду відправлено', input: 'Будь ласка, введіть MAC', ok: 'Ок' }, - about: { - title: 'Про NanoKVM', - information: 'Інформація', - ip: 'IP', - mdns: 'mDNS', - application: 'Версія додатку', - image: 'Версія образу', - deviceKey: 'Ключ пристрою', - queryFailed: 'Запит не вдався', - community: 'Спільнота' + power: { + title: 'Живлення', + reset: 'Скидання', + power: 'Живлення', + powerShort: 'Живлення (коротке натискання)', + powerLong: 'Живлення (довге натискання)' }, - update: { - title: 'Перевірити оновлення', - queryFailed: 'Не вдалося отримати версію', - updateFailed: 'Оновлення не вдалося. Будь ласка, спробуйте ще раз.', - isLatest: 'У вас вже остання версія.', - available: 'Доступне оновлення. Ви впевнені, що хочете оновити?', - updating: 'Оновлення розпочато. Будь ласка, зачекайте...', - confirm: 'Підтвердити', - cancel: 'Скасувати' - }, - virtualDevice: { - network: 'Віртуальна мережа', - disk: 'Віртуальний диск' - }, - tailscale: { - loading: 'Завантаження...', - notInstall: 'Tailscale не знайдено! Будь ласка, встановіть клієнт Tailscale.', - install: 'Встановити', - installing: 'Встановлення', - failed: 'Не вдалося встановити', - retry: 'Будь ласка, оновіть сторінку та спробуйте ще раз. Або спробуйте встановити вручну', - download: 'Завантажте', - package: 'пакет встановлення', - unzip: 'та розпакуйте його', - upTailscale: 'Завантажте tailscale до каталогу /usr/bin/ на NanoKVM', - upTailscaled: 'Завантажте tailscaled до каталогу /usr/sbin/ на NanoKVM', - refresh: 'Оновіть поточну сторінку', - notLogin: - "Пристрій ще не прив'язаний. Будь ласка, увійдіть і прив'яжіть цей пристрій до вашого облікового запису.", - urlPeriod: 'Ця URL-адреса дійсна протягом 10 хвилин', - login: 'Увійти', - loginSuccess: 'Успішний вхід', - enable: 'Увімкнути Tailscale', - deviceName: 'Назва пристрою', - deviceIP: 'IP пристрою', - account: 'Обліковий запис', - logout: 'Вийти', - logout2: 'Ви впевнені, що хочете вийти?' + settings: { + title: 'Settings', + about: { + title: 'Про NanoKVM', + information: 'Інформація', + ip: 'IP', + mdns: 'mDNS', + application: 'Версія додатку', + applicationTip: 'NanoKVM web application version', + image: 'Версія образу', + imageTip: 'NanoKVM system image version', + deviceKey: 'Ключ пристрою', + community: 'Спільнота' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Завантаження...', + notInstall: 'Tailscale не знайдено! Будь ласка, встановіть клієнт Tailscale.', + install: 'Встановити', + installing: 'Встановлення', + failed: 'Не вдалося встановити', + retry: 'Будь ласка, оновіть сторінку та спробуйте ще раз. Або спробуйте встановити вручну', + download: 'Завантажте', + package: 'пакет встановлення', + unzip: 'та розпакуйте його', + upTailscale: 'Завантажте tailscale до каталогу /usr/bin/ на NanoKVM', + upTailscaled: 'Завантажте tailscaled до каталогу /usr/sbin/ на NanoKVM', + refresh: 'Оновіть поточну сторінку', + notLogin: + "Пристрій ще не прив'язаний. Будь ласка, увійдіть і прив'яжіть цей пристрій до вашого облікового запису.", + urlPeriod: 'Ця URL-адреса дійсна протягом 10 хвилин', + login: 'Увійти', + loginSuccess: 'Успішний вхід', + enable: 'Увімкнути Tailscale', + deviceName: 'Назва пристрою', + deviceIP: 'IP пристрою', + account: 'Обліковий запис', + logout: 'Вийти', + logout2: 'Ви впевнені, що хочете вийти?' + }, + update: { + title: 'Перевірити оновлення', + queryFailed: 'Не вдалося отримати версію', + updateFailed: 'Оновлення не вдалося. Будь ласка, спробуйте ще раз.', + isLatest: 'У вас вже остання версія.', + available: 'Доступне оновлення. Ви впевнені, що хочете оновити?', + updating: 'Оновлення розпочато. Будь ласка, зачекайте...', + confirm: 'Підтвердити', + cancel: 'Скасувати' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/vi.ts b/web/src/i18n/locales/vi.ts index 75b05a2..421e236 100644 --- a/web/src/i18n/locales/vi.ts +++ b/web/src/i18n/locales/vi.ts @@ -1,25 +1,11 @@ const vi = { translation: { - language: 'Ngôn ngữ', - changePassword: 'Đổi mật khẩu', - logout: 'Đăng xuất', - settings: 'Cài đặt', - showMouse: 'Hiển thị chuột', - hideMouse: 'Ẩn chuột', - power: 'Nguồn', - reset: 'Đặt lại', - powerShort: 'Nguồn (nhấp ngắn)', - powerLong: 'Nguồn (nhấp dài)', - hddLed: 'Đèn HDD', - checkLibFailed: 'Kiểm tra thư viện runtime thất bại, vui lòng thử lại', - updateLibFailed: 'Cập nhật thư viện runtime thất bại, vui lòng thử lại', - updatingLib: 'Đang cập nhật thư viện runtime. Vui lòng làm mới trang sau khi cập nhật.', - checkForUpdate: 'Kiểm tra cập nhật', head: { desktop: 'Remote Desktop', login: 'Đăng nhập', changePassword: 'Đổi mật khẩu', - terminal: 'Terminal' + terminal: 'Terminal', + wifi: 'Wi-Fi' }, auth: { login: 'Đăng nhập', @@ -38,14 +24,28 @@ const vi = { illegalUsername: 'tên người dùng chứa ký tự không hợp lệ', illegalPassword: 'mật khẩu chứa ký tự không hợp lệ', forgetPassword: 'Quên mật khẩu', - resetPassword: 'Đặt lại mật khẩu', - reset1: 'Nếu bạn quên mật khẩu, vui lòng làm theo các bước sau để đặt lại:', - reset2: '1. Đăng nhập vào thiết bị NanoKVM qua SSH;', - reset3: '2. Xóa tệp trong thiết bị: ', - reset4: '3. Sử dụng tài khoản mặc định để đăng nhập: ', ok: 'OK', cancel: 'Hủy', - loginButtonText: 'Đăng nhập' + loginButtonText: 'Đăng nhập', + tips: { + reset1: + 'To reset the passwords, pressing and holding the BOOT button on the NanoKVM for 10 seconds.', + reset2: 'For detailed steps, please consult this document:', + reset3: 'Web default account:', + reset4: 'SSH default account:', + change1: 'Please note that this action will change the following passwords:', + change2: 'Web login password', + change3: 'System root password (SSH login password)', + change4: 'To reset the passwords, press and hold the BOOT button on the NanoKVM.' + } + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi for NanoKVM', + success: 'Please check the network status of NanoKVM and visit the new IP address.', + failed: 'Operation failed, please try again.', + confirmBtn: 'Ok', + finishBtn: 'Finished' }, screen: { video: 'Chế độ video', @@ -130,60 +130,113 @@ const vi = { confirm: 'OK' }, wol: { + title: 'Wake-on-LAN', sending: 'Đang gửi lệnh...', sent: 'Đã gửi lệnh', input: 'Vui lòng nhập địa chỉ MAC', ok: 'OK' }, - about: { - title: 'Giới thiệu về NanoKVM', - information: 'Thông tin', - ip: 'IP', - mdns: 'mDNS', - application: 'Phiên bản Ứng dụng', - image: 'Phiên bản Hình ảnh', - deviceKey: 'Khóa Thiết bị', - queryFailed: 'Truy vấn thất bại', - community: 'Cộng đồng' + power: { + title: 'Nguồn', + reset: 'Đặt lại', + power: 'Nguồn', + powerShort: 'Nguồn (nhấp ngắn)', + powerLong: 'Nguồn (nhấp dài)' }, - update: { - title: 'Kiểm tra cập nhật', - queryFailed: 'Lấy phiên bản thất bại', - updateFailed: 'Cập nhật thất bại. Vui lòng thử lại.', - isLatest: 'Bạn đã có phiên bản mới nhất.', - available: 'Có bản cập nhật mới. Bạn có chắc chắn muốn cập nhật không?', - updating: 'Bắt đầu cập nhật. Vui lòng chờ...', - confirm: 'Xác nhận', - cancel: 'Hủy' - }, - virtualDevice: { - network: 'Mạng Ảo', - disk: 'Đĩa Ảo' - }, - tailscale: { - loading: 'Đang tải...', - notInstall: 'Không tìm thấy Tailscale! Vui lòng cài đặt.', - install: 'Cài đặt', - installing: 'Đang cài đặt', - failed: 'Cài đặt thất bại', - retry: 'Vui lòng làm mới và thử lại. Hoặc thử cài đặt thủ công', - download: 'Tải xuống', - package: 'gói cài đặt', - unzip: 'và giải nén nó', - upTailscale: 'Tải tailscale lên thư mục /usr/bin/ của NanoKVM', - upTailscaled: 'Tải tailscaled lên thư mục /usr/sbin/ của NanoKVM', - refresh: 'Làm mới trang hiện tại', - notLogin: - 'Thiết bị chưa được liên kết. Vui lòng đăng nhập và liên kết thiết bị này với tài khoản của bạn.', - urlPeriod: 'URL này có hiệu lực trong 10 phút', - login: 'Đăng nhập', - loginSuccess: 'Đăng nhập thành công', - enable: 'Kích hoạt Tailscale', - deviceName: 'Tên Thiết bị', - deviceIP: 'IP Thiết bị', - account: 'Tài khoản', - logout: 'Đăng xuất', - logout2: 'Bạn có chắc chắn muốn đăng xuất không?' + settings: { + title: 'Settings', + about: { + title: 'Giới thiệu về NanoKVM', + information: 'Thông tin', + ip: 'IP', + mdns: 'mDNS', + application: 'Phiên bản Ứng dụng', + applicationTip: 'NanoKVM web application version', + image: 'Phiên bản Hình ảnh', + imageTip: 'NanoKVM system image version', + deviceKey: 'Khóa Thiết bị', + community: 'Cộng đồng' + }, + appearance: { + title: 'Appearance', + display: 'Display', + language: 'Language', + menuBar: 'Menu Bar', + menuBarDesc: 'Display icons in the menu bar' + }, + device: { + title: 'Device', + oled: { + title: 'OLED', + description: 'OLED screen automatically sleep', + 0: 'Never', + 15: '15 sec', + 30: '30 sec', + 60: '1 min', + 180: '3 min', + 300: '5 min', + 600: '10 min', + 1800: '30 min', + 3600: '1 hour' + }, + wifi: { + title: 'Wi-Fi', + description: 'Configure Wi-Fi', + setBtn: 'Config' + }, + disk: 'Virtual Disk', + diskDesc: 'Mount virtual U-disk on the remote host', + network: 'Virtual Network', + networkDesc: 'Mount virtual network card on the remote host', + memory: { + title: 'Memory optimization', + tip: 'When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory.', + disable: 'Disable' + } + }, + tailscale: { + title: 'Tailscale', + loading: 'Đang tải...', + notInstall: 'Không tìm thấy Tailscale! Vui lòng cài đặt.', + install: 'Cài đặt', + installing: 'Đang cài đặt', + failed: 'Cài đặt thất bại', + retry: 'Vui lòng làm mới và thử lại. Hoặc thử cài đặt thủ công', + download: 'Tải xuống', + package: 'gói cài đặt', + unzip: 'và giải nén nó', + upTailscale: 'Tải tailscale lên thư mục /usr/bin/ của NanoKVM', + upTailscaled: 'Tải tailscaled lên thư mục /usr/sbin/ của NanoKVM', + refresh: 'Làm mới trang hiện tại', + notLogin: + 'Thiết bị chưa được liên kết. Vui lòng đăng nhập và liên kết thiết bị này với tài khoản của bạn.', + urlPeriod: 'URL này có hiệu lực trong 10 phút', + login: 'Đăng nhập', + loginSuccess: 'Đăng nhập thành công', + enable: 'Kích hoạt Tailscale', + deviceName: 'Tên Thiết bị', + deviceIP: 'IP Thiết bị', + account: 'Tài khoản', + logout: 'Đăng xuất', + logout2: 'Bạn có chắc chắn muốn đăng xuất không?' + }, + update: { + title: 'Kiểm tra cập nhật', + queryFailed: 'Lấy phiên bản thất bại', + updateFailed: 'Cập nhật thất bại. Vui lòng thử lại.', + isLatest: 'Bạn đã có phiên bản mới nhất.', + available: 'Có bản cập nhật mới. Bạn có chắc chắn muốn cập nhật không?', + updating: 'Bắt đầu cập nhật. Vui lòng chờ...', + confirm: 'Xác nhận', + cancel: 'Hủy' + }, + account: { + title: 'Account', + webAccount: 'Web Account Name', + password: 'Password', + updateBtn: 'Update', + logoutBtn: 'Logout' + } } } }; diff --git a/web/src/i18n/locales/zh.ts b/web/src/i18n/locales/zh.ts index 18739b5..36736e3 100644 --- a/web/src/i18n/locales/zh.ts +++ b/web/src/i18n/locales/zh.ts @@ -1,25 +1,11 @@ const zh = { translation: { - currentVersion: '版本', - latestVersion: '最新版本:', - changePassword: '修改密码', - logout: '登出', - settings: '设置', - showMouse: '显示鼠标', - hideMouse: '隐藏鼠标', - power: '电源', - reset: '重启', - powerShort: '电源(短按)', - powerLong: '电源(长按)', - hddLed: '硬盘指示灯', - checkLibFailed: '检查运行库失败,请重试', - updateLibFailed: '更新运行库失败,请重试', - updatingLib: '正在更新运行库。更新完成后请刷新页面。', head: { desktop: '远程桌面', login: '登录', changePassword: '修改密码', - terminal: '终端' + terminal: '终端', + wifi: 'Wi-Fi' }, auth: { login: '登录', @@ -32,19 +18,32 @@ const zh = { invalidUser: '用户名或密码错误', error: '未知错误', changePassword: '修改密码', - changePasswordDesc: '为了您的设备安全,请修改网页登录密码。', + changePasswordDesc: '为了您的设备安全,请修改密码!', differentPassword: '两次密码不一致', illegalUsername: '用户名中包含非法字符', illegalPassword: '密码中包含非法字符', forgetPassword: '忘记密码', - resetPassword: '重置密码', - reset1: '如果您忘记了登录密码,请按照以下步骤重置密码:', - reset2: '1. 通过 SSH 登录到您的 NanoKVM 设备', - reset3: '2. 删除设备中的文件:', - reset4: '3. 使用默认的账号登录: ', ok: '确定', cancel: '取消', - loginButtonText: '登录' + loginButtonText: '登录', + tips: { + reset1: '长按 NanoKVM 上的 BOOT 按键 10 秒钟来重置帐号。', + reset2: '详细操作步骤可参考此文档:', + reset3: '网页默认帐号:', + reset4: 'SSH 默认帐号:', + change1: '请注意,此操作将同时更新以下密码:', + change2: '网页的登录密码', + change3: '系统 root 用户的密码(SSH 登录密码)', + change4: '如果您忘记了密码,需要长按 NanoKVM 上的 BOOT 按键来重置密码。' + } + }, + wifi: { + title: 'Wi-Fi', + description: '配置 NanoKVM Wi-Fi 信息', + success: '请检查 NanoKVM 的网络状态,并访问新的 IP 地址。', + failed: '操作失败,请重试。', + confirmBtn: '确定', + finishBtn: '完成' }, screen: { video: '视频模式', @@ -127,59 +126,112 @@ const zh = { confirm: '确定' }, wol: { + title: 'Wake-on-LAN', sending: '指令发送中...', sent: '指令已发送', input: '请输入MAC地址', ok: '确定' }, - about: { - title: '关于 NanoKVM', - information: '信息', - ip: 'IP', - mdns: 'mDNS', - application: '应用版本', - image: '镜像版本', - deviceKey: '设备码', - queryFailed: '查询失败', - community: '社区' + power: { + title: '电源', + reset: '重启', + power: '电源', + powerShort: '电源(短按)', + powerLong: '电源(长按)' }, - update: { - title: '检查更新', - queryFailed: '获取版本号失败', - updateFailed: '更新失败,请重试', - isLatest: '已经是最新版本。', - available: '有新的可用版本,确定要更新吗?', - updating: '更新中,请稍候...', - confirm: '确定', - cancel: '取消' - }, - virtualDevice: { - network: '虚拟网卡', - disk: '虚拟硬盘' - }, - tailscale: { - loading: '加载中...', - notInstall: '未检测到 Tailscale,请先安装', - install: '安装', - installing: '安装中', - failed: '安装失败', - retry: '请刷新后重试,或尝试手动安装', - download: '下载', - package: '安装包', - unzip: '并解压', - upTailscale: '将 tailscale 文件上传到 /usr/bin/ 目录', - upTailscaled: '将 tailscaled 文件上传到 /usr/sbin/ 目录', - refresh: '刷新页面', - notLogin: '该设备尚未绑定,请点击登录并将这台设备绑定到您的账号。', - urlPeriod: '该链接10分钟内有效', - login: '登录', - loginSuccess: '登录完成', - enable: '启用 Tailscale', - deviceName: '设备名称', - deviceIP: '设备地址', - account: '账号', - logout: '退出', - logout2: '确认退出?' + settings: { + title: '设置', + about: { + title: '关于 NanoKVM', + information: '信息', + ip: 'IP', + mdns: 'mDNS', + application: '应用版本', + applicationTip: 'NanoKVM 网页应用版本', + image: '镜像版本', + imageTip: 'NanoKVM 系统镜像版本', + deviceKey: '设备码', + community: '社区' + }, + appearance: { + title: '外观', + display: '显示', + language: '语言', + menuBar: '菜单栏', + menuBarDesc: '是否在菜单栏中显示图标' + }, + device: { + title: '设备', + oled: { + title: 'OLED', + description: '设置 OLED 屏幕自动休眠时间', + 0: '永不', + 15: '15秒', + 30: '30秒', + 60: '1分钟', + 180: '3分钟', + 300: '5分钟', + 600: '10分钟', + 1800: '30分钟', + 3600: '1小时' + }, + wifi: { + title: 'Wi-Fi', + description: '配置 Wi-Fi 信息', + setBtn: '设置' + }, + disk: '虚拟U盘', + diskDesc: '在远程主机中挂载虚拟U盘', + network: '虚拟网卡', + networkDesc: '在远程主机中挂载虚拟网卡', + memory: { + title: '内存优化', + tip: '当内存占用超过限制时,会更积极地执行垃圾回收来尝试释放内存', + disable: '关闭' + } + }, + tailscale: { + title: 'Tailscale', + loading: '加载中...', + notInstall: '未检测到 Tailscale,请先安装', + install: '安装', + installing: '安装中', + failed: '安装失败', + retry: '请刷新后重试,或尝试手动安装', + download: '下载', + package: '安装包', + unzip: '并解压', + upTailscale: '将 tailscale 文件上传到 /usr/bin/ 目录', + upTailscaled: '将 tailscaled 文件上传到 /usr/sbin/ 目录', + refresh: '刷新页面', + notLogin: '该设备尚未绑定,请点击登录并将这台设备绑定到您的账号。', + urlPeriod: '该链接10分钟内有效', + login: '登录', + loginSuccess: '登录完成', + enable: '启用 Tailscale', + deviceName: '设备名称', + deviceIP: '设备地址', + account: '账号', + logout: '退出', + logout2: '确认退出?' + }, + update: { + title: '检查更新', + queryFailed: '获取版本号失败', + updateFailed: '更新失败,请重试', + isLatest: '已经是最新版本。', + available: '有新的可用版本,确定要更新吗?', + updating: '更新中,请稍候...', + confirm: '确定', + cancel: '取消' + }, + account: { + title: '帐号', + webAccount: '网页帐号', + password: '密码', + updateBtn: '修改', + logoutBtn: '退出' + } } } }; diff --git a/web/src/i18n/locales/zh_tw.ts b/web/src/i18n/locales/zh_tw.ts index 93e225f..c53bd42 100644 --- a/web/src/i18n/locales/zh_tw.ts +++ b/web/src/i18n/locales/zh_tw.ts @@ -1,25 +1,11 @@ const zh_tw = { translation: { - language: '語言', - changePassword: '更改密碼', - logout: '登出', - settings: '設定', - showMouse: '顯示滑鼠游標', - hideMouse: '隱藏滑鼠游標', - power: '電源', - reset: '重新啟動', - powerShort: '電源 (短按)', - powerLong: '電源 (長按)', - hddLed: '硬碟 LED', - checkLibFailed: '檢查執行階段函式庫失敗,請重試', - updateLibFailed: '更新執行階段函式庫失敗,請重試', - updatingLib: '正在更新執行階段函式庫。更新完成後請重新整理頁面。', - checkForUpdate: '檢查更新', head: { desktop: '遠端桌面', login: '登入', changePassword: '更改密碼', - terminal: '終端機' + terminal: '終端機', + wifi: 'Wi-Fi' }, auth: { login: '登入', @@ -32,26 +18,39 @@ const zh_tw = { invalidUser: '使用者名稱或密碼錯誤', error: '非預期性錯誤', changePassword: '更改密碼', - changePasswordDesc: '為了您的裝置安全,請修改網頁登入密碼。', + changePasswordDesc: '為了您的裝置安全,請修改登入密碼。', differentPassword: '密碼不一致', illegalUsername: '使用者名稱包含非法字元', illegalPassword: '密碼包含非法字元', forgetPassword: '忘記密碼', - resetPassword: '重設密碼', - reset1: '如果您忘記密碼,請按照以下步驟重設:', - reset2: '1. 透過 SSH 登入 NanoKVM 設備;', - reset3: '2. 刪除設備中的檔案:', - reset4: '3. 使用預設帳號登入:', ok: '確定', cancel: '取消', loginButtonText: '登入', + tips: { + reset1: '长按 NanoKVM 上的 BOOT 按键 10 秒钟来重置帐号。', + reset2: '详细操作步骤可参考此文档:', + reset3: '网页默认帐号:', + reset4: 'SSH 默认帐号:', + change1: '请注意,此操作将同时更新以下密码:', + change2: '网页的登录密码', + change3: '系统 root 用户的密码(SSH 登录密码)', + change4: '如果您忘记了密码,需要长按 NanoKVM 上的 BOOT 按键来重置密码。' + } + }, + wifi: { + title: 'Wi-Fi', + description: '配置 NanoKVM Wi-Fi 信息', + success: '请检查 NanoKVM 的网络状态,并访问新的 IP 地址。', + failed: '操作失败,请重试。', + confirmBtn: '确定', + finishBtn: '完成' }, screen: { video: '影片模式', resolution: '解析度', auto: '自動', autoTips: - "在特定解析度下可能會出現畫面撕裂或滑鼠偏移的情況。考慮調整遠端主機的解析度或停用自動模式。", + '在特定解析度下可能會出現畫面撕裂或滑鼠偏移的情況。考慮調整遠端主機的解析度或停用自動模式。', fps: '影格速率', customizeFps: '自定義', quality: '品質', @@ -60,8 +59,7 @@ const zh_tw = { qualityMedium: '中', qualityLow: '低', frameDetect: '影格檢測', - frameDetectTip: - "計算影格之間的差異。當遠端主機畫面未偵測到任何變更時,停止傳輸視訊串流。" + frameDetectTip: '計算影格之間的差異。當遠端主機畫面未偵測到任何變更時,停止傳輸視訊串流。' }, keyboard: { paste: '貼上', @@ -88,8 +86,7 @@ const zh_tw = { loading: '載入中...', empty: '未找到任何內容', mountFailed: '掛載失敗', - mountDesc: - "在某些系統中,在掛載映像之前需要中斷遠端主機上的虛擬磁碟。", + mountDesc: '在某些系統中,在掛載映像之前需要中斷遠端主機上的虛擬磁碟。', tips: { title: '如何上傳', usb1: '透過 USB 將 NanoKVM 連接到您的電腦。', @@ -129,60 +126,112 @@ const zh_tw = { confirm: '確定' }, wol: { + title: 'Wake-on-LAN', sending: '發送指令中...', sent: '指令已發送', input: '請輸入 MAC 位址', ok: '確定' }, - about: { - title: '關於 NanoKVM', - information: '資訊', - ip: 'IP', - mdns: 'mDNS', - application: '應用程式版本', - image: '映像版本', - deviceKey: '設備序號', - queryFailed: '查詢失敗', - community: '社群' + power: { + title: '電源', + reset: '重新啟動', + power: '電源', + powerShort: '電源 (短按)', + powerLong: '電源 (長按)' }, - update: { - title: '檢查更新', - queryFailed: '取得版本失敗', - updateFailed: '更新失敗。請重試。', - isLatest: '您已經擁有最新版本。', - available: '有可用更新。確定要更新嗎?', - updating: '正在更新。請稍等...', - confirm: '確定', - cancel: '取消' - }, - virtualDevice: { - network: '虛擬網路', - disk: '虛擬磁碟' - }, - tailscale: { - loading: '載入中...', - notInstall: 'Tailscale 未找到!請先安裝。', - install: '安裝', - installing: '安裝中', - failed: '安裝失敗', - retry: '請重新整理並重試。或嘗試手動安裝', - download: '下載', - package: '安裝包', - unzip: '並解壓縮它', - upTailscale: '將 tailscale 上傳到 NanoKVM 目錄 /usr/bin/', - upTailscaled: '將 tailscale 上傳到 NanoKVM 目錄 /usr/sbin/', - refresh: '重新整理頁面', - notLogin: - '設備尚未綁定。請登入並將該裝置綁定到您的帳戶。', - urlPeriod: '此網址有效期限為 10 分鐘', - login: '登入', - loginSuccess: '登入成功', - enable: '啟用 Tailscale', - deviceName: '裝置名稱', - deviceIP: '裝置 IP', - account: '帳號', - logout: '登出', - logout2: '確認登出?' + settings: { + title: '设置', + about: { + title: '關於 NanoKVM', + information: '資訊', + ip: 'IP', + mdns: 'mDNS', + application: '應用程式版本', + applicationTip: 'NanoKVM 网页应用版本', + image: '映像版本', + imageTip: 'NanoKVM 系统镜像版本', + deviceKey: '設備序號', + community: '社群' + }, + appearance: { + title: '外观', + display: '显示', + language: '语言', + menuBar: '菜单栏', + menuBarDesc: '是否在菜单栏中显示图标' + }, + device: { + title: '设备', + oled: { + title: 'OLED', + description: '设置 OLED 屏幕自动休眠时间', + 0: '永不', + 15: '15秒', + 30: '30秒', + 60: '1分钟', + 180: '3分钟', + 300: '5分钟', + 600: '10分钟', + 1800: '30分钟', + 3600: '1小时' + }, + wifi: { + title: 'Wi-Fi', + description: '配置 Wi-Fi 信息', + setBtn: '设置' + }, + disk: '虚拟U盘', + diskDesc: '在远程主机中挂载虚拟U盘', + network: '虚拟网卡', + networkDesc: '在远程主机中挂载虚拟网卡', + memory: { + title: '内存优化', + tip: '当内存占用超过限制时,会更积极地执行垃圾回收来尝试释放内存', + disable: '关闭' + } + }, + tailscale: { + title: 'Tailscale', + loading: '載入中...', + notInstall: 'Tailscale 未找到!請先安裝。', + install: '安裝', + installing: '安裝中', + failed: '安裝失敗', + retry: '請重新整理並重試。或嘗試手動安裝', + download: '下載', + package: '安裝包', + unzip: '並解壓縮它', + upTailscale: '將 tailscale 上傳到 NanoKVM 目錄 /usr/bin/', + upTailscaled: '將 tailscale 上傳到 NanoKVM 目錄 /usr/sbin/', + refresh: '重新整理頁面', + notLogin: '設備尚未綁定。請登入並將該裝置綁定到您的帳戶。', + urlPeriod: '此網址有效期限為 10 分鐘', + login: '登入', + loginSuccess: '登入成功', + enable: '啟用 Tailscale', + deviceName: '裝置名稱', + deviceIP: '裝置 IP', + account: '帳號', + logout: '登出', + logout2: '確認登出?' + }, + update: { + title: '檢查更新', + queryFailed: '取得版本失敗', + updateFailed: '更新失敗。請重試。', + isLatest: '您已經擁有最新版本。', + available: '有可用更新。確定要更新嗎?', + updating: '正在更新。請稍等...', + confirm: '確定', + cancel: '取消' + }, + account: { + title: '帐号', + webAccount: '网页帐号', + password: '密码', + updateBtn: '修改', + logoutBtn: '退出' + } } } }; diff --git a/web/src/jotai/settings.ts b/web/src/jotai/settings.ts index c115d37..f4336a9 100644 --- a/web/src/jotai/settings.ts +++ b/web/src/jotai/settings.ts @@ -1,4 +1,4 @@ import { atom } from 'jotai'; -// is settings popover opened -export const isSettingsOpenAtom = atom(false); +// menu bar disabled items +export const menuDisabledItemsAtom = atom([]); diff --git a/web/src/lib/localstorage.ts b/web/src/lib/localstorage.ts index 215be15..d59c011 100644 --- a/web/src/lib/localstorage.ts +++ b/web/src/lib/localstorage.ts @@ -9,7 +9,8 @@ const MOUSE_STYLE_KEY = 'nano-kvm-mouse-style'; const MOUSE_MODE_KEY = 'nano-kvm-mouse-mode'; const SKIP_UPDATE_KEY = 'nano-kvm-check-update'; const KEYBOARD_LAYOUT_KEY = 'nano-kvm-keyboard-layout'; -const SKIP_MODIFY_PASSWORD = 'nano-kvm-skip-modify-password'; +const SKIP_MODIFY_PASSWORD_KEY = 'nano-kvm-skip-modify-password'; +const MENU_DISABLED_ITEMS_KEY = 'nano-kvm-menu-disabled-items'; type ItemWithExpiry = { value: string; @@ -127,10 +128,20 @@ export function getKeyboardLayout() { export function setSkipModifyPassword(skip: boolean) { const expiry = 3 * 24 * 60 * 60 * 1000; // 3 days - setWithExpiry(SKIP_MODIFY_PASSWORD, String(skip), expiry); + setWithExpiry(SKIP_MODIFY_PASSWORD_KEY, String(skip), expiry); } export function getSkipModifyPassword() { - const skip = getWithExpiry(SKIP_MODIFY_PASSWORD); + const skip = getWithExpiry(SKIP_MODIFY_PASSWORD_KEY); return skip ? Boolean(skip) : false; } + +export function setMenuDisabledItems(items: string[]) { + const value = JSON.stringify(items); + localStorage.setItem(MENU_DISABLED_ITEMS_KEY, value); +} + +export function getMenuDisabledItems(): string[] { + const value = localStorage.getItem(MENU_DISABLED_ITEMS_KEY); + return value ? JSON.parse(value) : []; +} diff --git a/web/src/pages/auth/login/tips.tsx b/web/src/pages/auth/login/tips.tsx index ae3d1a3..6a04491 100644 --- a/web/src/pages/auth/login/tips.tsx +++ b/web/src/pages/auth/login/tips.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Button, Modal, Typography } from 'antd'; +import { Button, Card, Modal, Typography } from 'antd'; import { useTranslation } from 'react-i18next'; const { Text } = Typography; @@ -19,32 +19,43 @@ export const Tips = () => { return ( <> {t('auth.forgetPassword')} -
-
{t('auth.reset1')}
-
{t('auth.reset2')}
-
- {t('auth.reset3')} - /etc/kvm/pwd + +
+
{t('auth.tips.reset1')}
+ +
+ {t('auth.tips.reset2')} + + wiki + +
+ +
    +
  • + {t('auth.tips.reset3')} + admin/admin +
  • +
  • + {t('auth.tips.reset4')} + root/root +
  • +
-
- {t('auth.reset4')} - admin/admin -
-
+