diff --git a/CHANGELOG.md b/CHANGELOG.md index e868ba7..d5c3f55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## v0.0.4 + +* Add: i18n support + +* 新增:支持中英文国际化 + + + ## v0.0.3 * Add: network IO speed monitoring. diff --git a/README.ZH.md b/README.ZH.md index b838873..46a1461 100644 --- a/README.ZH.md +++ b/README.ZH.md @@ -16,8 +16,7 @@ 现在暂时只支持本地安装,暂时还没有Docker等安装方式。 -
-本地安装 +### 本地安装 * 从 [Releases](https://github.com/XZB-1248/Spark/releases) 页面下载对应系统的可执行文件。 * 修改配置文件,特别是salt,需要修改成你自己的。 @@ -36,8 +35,6 @@ * 在管理页面中生成客户端,并部署到设备上。 * 现在就可以控制这个设备了。 -
- --- ## **特性** diff --git a/README.md b/README.md index 72b5b08..d496862 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,7 @@ that allow you to control all your devices via browser anywhere. Only local installation are available yet. -
-Local installation: - +### Local installation * Get prebuilt executable file from [Releases](https://github.com/XZB-1248/Spark/releases) page. * Modify configuration file and set your own salt. @@ -27,12 +25,10 @@ Only local installation are available yet. } ``` -* Run it and browser the address:port you've just set. +* Run it and browse the address:port you've just set. * Generate client online and execute it on your device. * Now you can control your device. -
- --- ## **Features** diff --git a/client/config/config.go b/client/config/config.go index 453e93b..520cf71 100644 --- a/client/config/config.go +++ b/client/config/config.go @@ -20,7 +20,7 @@ type Cfg struct { // none var CfgBuffer = "\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19" -// COMMIT means this commit hash, for auto upgrade. +// COMMIT means this commit hash, help to identify version and self upgrade. var COMMIT = `` var Config Cfg diff --git a/client/core/core.go b/client/core/core.go index 1241803..8351009 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -12,9 +12,6 @@ import ( "Spark/utils" "encoding/hex" "errors" - ws "github.com/gorilla/websocket" - "github.com/imroc/req/v3" - "github.com/kataras/golog" "io/ioutil" "net/http" "os" @@ -22,6 +19,10 @@ import ( "runtime" "strconv" "time" + + ws "github.com/gorilla/websocket" + "github.com/imroc/req/v3" + "github.com/kataras/golog" ) // simplified type of map @@ -78,6 +79,9 @@ func connectWS() (*common.Conn, error) { return nil, errNoSecretHeader } secret, err := hex.DecodeString(header[0]) + if err != nil { + return nil, err + } return &common.Conn{Conn: wsConn, Secret: secret}, nil } @@ -246,13 +250,10 @@ func handleAct(pack modules.Packet, wsConn *common.Conn) { if err != nil { common.SendCb(modules.Packet{Act: `initTerminal`, Code: 1, Msg: err.Error()}, pack, wsConn) } - break case `inputTerminal`: terminal.InputTerminal(pack) - break case `killTerminal`: terminal.KillTerminal(pack) - break case `listFiles`: path := `/` if val, ok := pack.Data[`path`]; ok { @@ -293,11 +294,11 @@ func handleAct(pack modules.Packet, wsConn *common.Conn) { { tempVal, ok := pack.Data[`file`] if !ok { - common.SendCb(modules.Packet{Code: 1, Msg: `未知错误`}, pack, wsConn) + common.SendCb(modules.Packet{Code: 1, Msg: `${i18n|unknownError}`}, pack, wsConn) return } if path, ok = tempVal.(string); !ok { - common.SendCb(modules.Packet{Code: 1, Msg: `未知错误`}, pack, wsConn) + common.SendCb(modules.Packet{Code: 1, Msg: `${i18n|unknownError}`}, pack, wsConn) return } tempVal, ok = pack.Data[`start`] @@ -316,7 +317,7 @@ func handleAct(pack modules.Packet, wsConn *common.Conn) { } } if end > 0 && end < start { - common.SendCb(modules.Packet{Code: 1, Msg: `文件范围错误`}, pack, wsConn) + common.SendCb(modules.Packet{Code: 1, Msg: `${i18n|invalidFileRange}`}, pack, wsConn) return } } @@ -334,12 +335,12 @@ func handleAct(pack modules.Packet, wsConn *common.Conn) { case `killProcess`: pidStr, ok := pack.Data[`pid`] if !ok { - common.SendCb(modules.Packet{Code: 1, Msg: `未知错误`}, pack, wsConn) + common.SendCb(modules.Packet{Code: 1, Msg: `${i18n|unknownError}`}, pack, wsConn) return } pid, err := strconv.ParseInt(pidStr.(string), 10, 32) if err != nil { - common.SendCb(modules.Packet{Code: 1, Msg: `未知错误`}, pack, wsConn) + common.SendCb(modules.Packet{Code: 1, Msg: `${i18n|unknownError}`}, pack, wsConn) return } err = process.KillProcess(int32(pid)) @@ -351,7 +352,6 @@ func handleAct(pack modules.Packet, wsConn *common.Conn) { default: common.SendCb(modules.Packet{Code: 0}, pack, wsConn) } - return } func heartbeat(wsConn *common.Conn) error { diff --git a/client/service/file/file.go b/client/service/file/file.go index f17edc2..01f0ca4 100644 --- a/client/service/file/file.go +++ b/client/service/file/file.go @@ -3,11 +3,12 @@ package file import ( "Spark/client/config" "errors" - "github.com/imroc/req/v3" "io" "io/ioutil" "os" "strconv" + + "github.com/imroc/req/v3" ) type file struct { @@ -58,7 +59,7 @@ func UploadFile(path, trigger string, start, end int64) error { `FileSize`: strconv.FormatInt(size, 10), } if size < end { - return errors.New(`文件大小有误`) + return errors.New(`Invalid file size.`) } if end == 0 { uploadReq.RawRequest.ContentLength = size - start diff --git a/client/service/terminal/terminal.go b/client/service/terminal/terminal.go index bdf8559..2222e0c 100644 --- a/client/service/terminal/terminal.go +++ b/client/service/terminal/terminal.go @@ -7,14 +7,15 @@ import ( "bytes" "encoding/hex" "errors" - "golang.org/x/text/encoding/simplifiedchinese" - "golang.org/x/text/transform" "io" "io/ioutil" "os" "os/exec" "runtime" "time" + + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/transform" ) type terminal struct { @@ -124,12 +125,12 @@ func InputTerminal(pack modules.Packet) error { } val, ok = terminals.Get(termUUID) if !ok { - common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `终端已退出`}, pack, common.WSConn) + common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `${i18n|terminalSessionClosed}`}, pack, common.WSConn) return nil } terminal, ok := val.(*terminal) if !ok { - common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `终端已退出`}, pack, common.WSConn) + common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `${i18n|terminalSessionClosed}`}, pack, common.WSConn) return nil } @@ -157,13 +158,13 @@ func KillTerminal(pack modules.Packet) error { } val, ok = terminals.Get(termUUID) if !ok { - common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `终端已退出`}, pack, common.WSConn) + common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `${i18n|terminalSessionClosed}`}, pack, common.WSConn) return nil } terminal, ok := val.(*terminal) if !ok { terminals.Remove(termUUID) - common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `终端已退出`}, pack, common.WSConn) + common.SendCb(modules.Packet{Act: `quitTerminal`, Msg: `${i18n|terminalSessionClosed}`}, pack, common.WSConn) return nil } doKillTerminal(terminal) diff --git a/go.mod b/go.mod index 1345dee..b3f90f7 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,6 @@ require ( golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect golang.org/x/net v0.0.0-20220111093109-d55c255bac03 // indirect golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/text v0.3.7 gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/server/common/event.go b/server/common/event.go index 27f1763..b2337fc 100644 --- a/server/common/event.go +++ b/server/common/event.go @@ -16,8 +16,8 @@ type EventCallback func(modules.Packet, *melody.Session) var eventTable = cmap.New() -// CallEvent 负责判断packet中的Callback字段,如果存在该字段, -// 就会调用event中的函数,并在调用完成之后通过chan通知addOnceEvent调用方 +// CallEvent tries to call the callback with the given uuid +// after that, it will notify the caller via the channel func CallEvent(pack modules.Packet, session *melody.Session) { if len(pack.Event) == 0 { return @@ -40,8 +40,9 @@ func CallEvent(pack modules.Packet, session *melody.Session) { } } -// AddEventOnce 会添加一个一次性的回调命令,client可以对事件成功与否进行回复 -// trigger一般是uuid,以此尽可能保证事件的独一无二 +// AddEventOnce adds a new event only once and client +// can call back the event with the given event trigger. +// Event trigger should be uuid to make every event unique. func AddEventOnce(fn EventCallback, connUUID, trigger string, timeout time.Duration) bool { done := make(chan bool) ev := &event{ @@ -59,8 +60,8 @@ func AddEventOnce(fn EventCallback, connUUID, trigger string, timeout time.Durat } } -// AddEvent 会添加一个持续的回调命令,client可以对事件成功与否进行回复 -// trigger一般是uuid,以此尽可能保证事件的独一无二 +// AddEvent adds a new event and client can call back +// the event with the given event trigger. func AddEvent(fn EventCallback, connUUID, trigger string) { ev := &event{ connection: connUUID, @@ -70,7 +71,7 @@ func AddEvent(fn EventCallback, connUUID, trigger string) { eventTable.Set(trigger, ev) } -// RemoveEvent 会删除特定的回调命令 +// RemoveEvent deletes the event with the given event trigger. func RemoveEvent(trigger string) { eventTable.Remove(trigger) } diff --git a/server/handler/file.go b/server/handler/file.go index 094294c..dbf9d5a 100644 --- a/server/handler/file.go +++ b/server/handler/file.go @@ -25,7 +25,7 @@ func removeDeviceFile(ctx *gin.Context) { Device string `json:"device" yaml:"device" form:"device"` } if ctx.ShouldBind(&form) != nil || (len(form.Conn) == 0 && len(form.Device) == 0) { - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } target := `` @@ -34,13 +34,13 @@ func removeDeviceFile(ctx *gin.Context) { ok := false target, ok = common.CheckDevice(form.Device) if !ok { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } else { target = form.Conn if !common.Devices.Has(target) { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } @@ -53,7 +53,7 @@ func removeDeviceFile(ctx *gin.Context) { } }, target, trigger, 5*time.Second) if !ok { - ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `响应超时`}) + ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) } } @@ -65,7 +65,7 @@ func listDeviceFiles(ctx *gin.Context) { Device string `json:"device" yaml:"device" form:"device"` } if ctx.ShouldBind(&form) != nil || (len(form.Conn) == 0 && len(form.Device) == 0) { - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } connUUID := `` @@ -74,13 +74,13 @@ func listDeviceFiles(ctx *gin.Context) { ok := false connUUID, ok = common.CheckDevice(form.Device) if !ok { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } else { connUUID = form.Conn if !common.Devices.Has(connUUID) { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } @@ -93,7 +93,7 @@ func listDeviceFiles(ctx *gin.Context) { } }, connUUID, trigger, 5*time.Second) if !ok { - ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `响应超时`}) + ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) } } @@ -106,7 +106,7 @@ func getDeviceFile(ctx *gin.Context) { Device string `json:"device" yaml:"device" form:"device"` } if ctx.ShouldBind(&form) != nil || (len(form.Conn) == 0 && len(form.Device) == 0) { - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } target := `` @@ -115,13 +115,13 @@ func getDeviceFile(ctx *gin.Context) { ok := false target, ok = common.CheckDevice(form.Device) if !ok { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } else { target = form.Conn if !common.Devices.Has(target) { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } @@ -179,13 +179,13 @@ func getDeviceFile(ctx *gin.Context) { val, ok := p.Data[`request`] if !ok { wait <- false - ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `文件上传失败`}) + ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|fileUploadFailed}`}) return } req, ok := val.(*http.Request) if !ok || req == nil || req.Body == nil { wait <- false - ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `文件上传失败`}) + ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|fileUploadFailed}`}) return } @@ -234,7 +234,7 @@ func getDeviceFile(ctx *gin.Context) { case <-time.After(5 * time.Second): if !called { common.RemoveEvent(trigger) - ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `响应超时`}) + ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) } else { <-wait } @@ -251,13 +251,13 @@ func putDeviceFile(ctx *gin.Context) { trigger := ctx.GetHeader(`Trigger`) if len(trigger) == 0 { original.Close() - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } if len(errMsg) > 0 { common.CallEvent(modules.Packet{ Code: 1, - Msg: fmt.Sprintf(`文件上传失败:%v`, errMsg), + Msg: fmt.Sprintf(`${i18n|fileUploadFailed}: %v`, errMsg), Event: trigger, }, nil) original.Close() diff --git a/server/handler/generate.go b/server/handler/generate.go index bc3e8b4..f2a7382 100644 --- a/server/handler/generate.go +++ b/server/handler/generate.go @@ -58,12 +58,12 @@ func checkClient(ctx *gin.Context) { Secure string `json:"secure" yaml:"secure" form:"secure"` } if err := ctx.ShouldBind(&form); err != nil { - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } _, err := common.BuiltFS.Open(fmt.Sprintf(`/%v_%v`, form.OS, form.Arch)) if err != nil { - ctx.JSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `该系统或架构的客户端尚未编译`}) + ctx.JSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`}) return } _, err = genConfig(clientCfg{ @@ -76,10 +76,10 @@ func checkClient(ctx *gin.Context) { }) if err != nil { if err == errTooLargeEntity { - ctx.JSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1, Msg: `配置信息过长`}) + ctx.JSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1, Msg: `${i18n|tooLargeConfig}`}) return } - ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `配置文件生成失败`}) + ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`}) return } ctx.JSON(http.StatusOK, modules.Packet{Code: 0}) @@ -95,18 +95,18 @@ func generateClient(ctx *gin.Context) { Secure string `json:"secure" yaml:"secure" form:"secure"` } if err := ctx.ShouldBind(&form); err != nil { - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } tpl, err := common.BuiltFS.Open(fmt.Sprintf(`/%v_%v`, form.OS, form.Arch)) if err != nil { - ctx.JSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `该系统或架构的客户端尚未编译`}) + ctx.JSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`}) return } clientUUID := utils.GetUUID() clientKey, err := common.EncAES(clientUUID, config.Config.StdSalt) if err != nil { - ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `配置文件生成失败`}) + ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`}) return } cfgBytes, err := genConfig(clientCfg{ @@ -119,10 +119,10 @@ func generateClient(ctx *gin.Context) { }) if err != nil { if err == errTooLargeEntity { - ctx.JSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1, Msg: `配置信息过长`}) + ctx.JSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1, Msg: `${i18n|tooLargeConfig}`}) return } - ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `配置文件生成失败`}) + ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`}) return } ctx.Header(`Accept-Ranges`, `none`) diff --git a/server/handler/handler.go b/server/handler/handler.go index fd7721d..720528d 100644 --- a/server/handler/handler.go +++ b/server/handler/handler.go @@ -45,7 +45,7 @@ func checkUpdate(ctx *gin.Context) { } if err := ctx.ShouldBind(&form); err != nil { golog.Error(err) - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } if form.Commit == config.COMMIT { @@ -54,7 +54,7 @@ func checkUpdate(ctx *gin.Context) { } tpl, err := common.BuiltFS.Open(fmt.Sprintf(`/%v_%v`, form.OS, form.Arch)) if err != nil { - ctx.JSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `该系统或架构的客户端尚未编译`}) + ctx.JSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`}) return } @@ -107,13 +107,13 @@ func putScreenshot(ctx *gin.Context) { errMsg := ctx.GetHeader(`Error`) trigger := ctx.GetHeader(`Trigger`) if len(trigger) == 0 { - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } if len(errMsg) > 0 { common.CallEvent(modules.Packet{ Code: 1, - Msg: fmt.Sprintf(`截图失败:%v`, errMsg), + Msg: fmt.Sprintf(`${i18n|screenshotFailed}: %v`, errMsg), Event: trigger, }, nil) ctx.JSON(http.StatusOK, modules.Packet{Code: 0}) @@ -123,10 +123,10 @@ func putScreenshot(ctx *gin.Context) { if len(data) == 0 { msg := `` if err != nil { - msg = fmt.Sprintf(`截图读取失败:%v`, err) + msg = fmt.Sprintf(`${i18n|screenshotObtainFailed}: %v`, err) ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: msg}) } else { - msg = `截图失败:未知错误` + msg = `${i18n|screenshotFailed}: ${i18n|unknownError}` ctx.JSON(http.StatusOK, modules.Packet{Code: 0}) } common.CallEvent(modules.Packet{ @@ -153,7 +153,7 @@ func getScreenshot(ctx *gin.Context) { Device string `json:"device" yaml:"device" form:"device"` } if ctx.ShouldBind(&form) != nil || (len(form.Conn) == 0 && len(form.Device) == 0) { - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } target := `` @@ -162,13 +162,13 @@ func getScreenshot(ctx *gin.Context) { ok := false target, ok = common.CheckDevice(form.Device) if !ok { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } else { target = form.Conn if !common.Devices.Has(target) { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } @@ -179,19 +179,19 @@ func getScreenshot(ctx *gin.Context) { } else { data, ok := p.Data[`screenshot`] if !ok { - ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `截图获取失败`}) + ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|screenshotObtainFailed}`}) return } screenshot, ok := data.([]byte) if !ok { - ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `截图获取失败`}) + ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|screenshotObtainFailed}`}) return } ctx.Data(200, `image/png`, screenshot) } }, target, trigger, 5*time.Second) if !ok { - ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `响应超时`}) + ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) } } @@ -216,7 +216,7 @@ func callDevice(ctx *gin.Context) { } act := ctx.Param(`act`) if ctx.ShouldBind(&form) != nil || len(act) == 0 || (len(form.Conn) == 0 && len(form.Device) == 0) { - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } connUUID := `` @@ -225,13 +225,13 @@ func callDevice(ctx *gin.Context) { ok := false connUUID, ok = common.CheckDevice(form.Device) if !ok { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } else { connUUID = form.Conn if !common.Devices.Has(connUUID) { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } @@ -244,7 +244,7 @@ func callDevice(ctx *gin.Context) { } }, connUUID, trigger, 5*time.Second) if !ok { - ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `响应超时`}) + ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) } } diff --git a/server/handler/process.go b/server/handler/process.go index fa0287f..a70e32f 100644 --- a/server/handler/process.go +++ b/server/handler/process.go @@ -18,7 +18,7 @@ func listDeviceProcesses(ctx *gin.Context) { Device string `json:"device" yaml:"device" form:"device"` } if ctx.ShouldBind(&form) != nil || (len(form.Conn) == 0 && len(form.Device) == 0) { - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } connUUID := `` @@ -27,13 +27,13 @@ func listDeviceProcesses(ctx *gin.Context) { ok := false connUUID, ok = common.CheckDevice(form.Device) if !ok { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } else { connUUID = form.Conn if !common.Devices.Has(connUUID) { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } @@ -46,7 +46,7 @@ func listDeviceProcesses(ctx *gin.Context) { } }, connUUID, trigger, 5*time.Second) if !ok { - ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `响应超时`}) + ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) } } @@ -59,7 +59,7 @@ func killDeviceProcess(ctx *gin.Context) { Device string `json:"device" yaml:"device" form:"device"` } if ctx.ShouldBind(&form) != nil || (len(form.Conn) == 0 && len(form.Device) == 0) { - ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`}) + ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) return } target := `` @@ -68,13 +68,13 @@ func killDeviceProcess(ctx *gin.Context) { ok := false target, ok = common.CheckDevice(form.Device) if !ok { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } else { target = form.Conn if !common.Devices.Has(target) { - ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `未找到该设备`}) + ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) return } } @@ -87,6 +87,6 @@ func killDeviceProcess(ctx *gin.Context) { } }, target, trigger, 5*time.Second) if !ok { - ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `响应超时`}) + ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) } } diff --git a/server/handler/terminal.go b/server/handler/terminal.go index 69a4bca..023bb22 100644 --- a/server/handler/terminal.go +++ b/server/handler/terminal.go @@ -29,31 +29,31 @@ func init() { wsTerminals.HandleConnect(func(session *melody.Session) { device, ok := session.Get(`Device`) if !ok { - simpleSendPack(modules.Packet{Act: `warn`, Msg: `终端创建失败`}, session) + simpleSendPack(modules.Packet{Act: `warn`, Msg: `${i18n|terminalSessionCreationFailed}`}, session) session.Close() return } val, ok := session.Get(`Terminal`) if !ok { - simpleSendPack(modules.Packet{Act: `warn`, Msg: `终端创建失败`}, session) + simpleSendPack(modules.Packet{Act: `warn`, Msg: `${i18n|terminalSessionCreationFailed}`}, session) session.Close() return } termUUID, ok := val.(string) if !ok { - simpleSendPack(modules.Packet{Act: `warn`, Msg: `终端创建失败`}, session) + simpleSendPack(modules.Packet{Act: `warn`, Msg: `${i18n|terminalSessionCreationFailed}`}, session) session.Close() return } connUUID, ok := common.CheckDevice(device.(string)) if !ok { - simpleSendPack(modules.Packet{Act: `warn`, Msg: `设备不存在或已经离线`}, session) + simpleSendPack(modules.Packet{Act: `warn`, Msg: `${i18n|deviceNotExists}`}, session) session.Close() return } deviceConn, ok := common.Melody.GetSessionByUUID(connUUID) if !ok { - simpleSendPack(modules.Packet{Act: `warn`, Msg: `设备不存在或已经离线`}, session) + simpleSendPack(modules.Packet{Act: `warn`, Msg: `${i18n|deviceNotExists}`}, session) session.Close() return } @@ -99,7 +99,7 @@ func init() { go common.WSHealthCheck(wsTerminals) } -// initTerminal 负责处理terminal的websocket握手事务 +// initTerminal handles terminal websocket handshake event func initTerminal(ctx *gin.Context) { if !ctx.IsWebsocket() { ctx.Status(http.StatusUpgradeRequired) @@ -133,15 +133,17 @@ func initTerminal(ctx *gin.Context) { }) } -// eventWrapper 会包装一个eventCb,当收到与浏览器session对应的device响应时, -// 会自动把数据转发给浏览器端 +// eventWrapper returns a eventCb function that will be called when +// device need to send a packet to browser terminal func eventWrapper(terminal *terminal) common.EventCallback { return func(pack modules.Packet, device *melody.Session) { if pack.Act == `initTerminal` { if pack.Code != 0 { - msg := `终端创建失败:未知错误` + msg := `${i18n|terminalSessionCreationFailed}` if len(pack.Msg) > 0 { - msg = `终端创建失败:` + pack.Msg + msg += pack.Msg + } else { + msg += `${i18n|unknownError}` } simpleSendPack(modules.Packet{Act: `warn`, Msg: msg}, terminal.session) terminals.Remove(terminal.termUUID) @@ -151,7 +153,7 @@ func eventWrapper(terminal *terminal) common.EventCallback { return } if pack.Act == `quitTerminal` { - msg := `终端已退出` + msg := `${i18n|terminalSessionClosed}` if len(pack.Msg) > 0 { msg = pack.Msg } diff --git a/server/main.go b/server/main.go index 96d7a13..0a7af25 100644 --- a/server/main.go +++ b/server/main.go @@ -34,16 +34,16 @@ func main() { data, err := ioutil.ReadFile(`./Config.json`) if err != nil { - golog.Fatal(`读取配置文件失败:`, err) + golog.Fatal(`Failed to read config file: `, err) return } err = utils.JSON.Unmarshal(data, &config.Config) if err != nil { - golog.Fatal(`解析配置文件失败:`, err) + golog.Fatal(`Failed to parse config file: `, err) return } if len(config.Config.Salt) > 24 { - golog.Fatal(`Salt的长度不能超过24位`) + golog.Fatal(`Length of Salt should be less than 24.`) return } config.Config.StdSalt = []byte(config.Config.Salt) @@ -52,12 +52,12 @@ func main() { webFS, err := fs.NewWithNamespace(`web`) if err != nil { - golog.Fatal(`加载静态资源失败:`, err) + golog.Fatal(`Failed to load static resources: `, err) return } common.BuiltFS, err = fs.NewWithNamespace(`built`) if err != nil { - golog.Fatal(`加载预编译客户端失败:`, err) + golog.Fatal(`Failed to load prebuilt clients: `, err) return } app := gin.New() @@ -81,7 +81,7 @@ func main() { golog.Fatal(`Failed to bind address: `, err) } }() - quit := make(chan os.Signal) + quit := make(chan os.Signal, 3) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit golog.Warn(`Server is shutting down ...`) @@ -91,9 +91,7 @@ func main() { if err := srv.Shutdown(ctx); err != nil { golog.Fatal(`Server shutdown: `, err) } - select { - case <-ctx.Done(): - } + <-ctx.Done() golog.Info(`Server exited,`) } diff --git a/web/package-lock.json b/web/package-lock.json index a00fed5..fbcf0bd 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -169,38 +169,128 @@ } }, "@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", "dev": true }, "@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.9.tgz", + "integrity": "sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.17.2", - "@babel/parser": "^7.17.3", + "@babel/generator": "^7.17.9", + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.9", + "@babel/parser": "^7.17.9", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", + "@babel/traverse": "^7.17.9", "@babel/types": "^7.17.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", + "json5": "^2.2.1", "semver": "^6.3.0" + }, + "dependencies": { + "@babel/compat-data": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", + "dev": true + }, + "@babel/generator": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.9.tgz", + "integrity": "sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ==", + "dev": true, + "requires": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + } + }, + "@babel/helper-function-name": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", + "dev": true, + "requires": { + "@babel/template": "^7.16.7", + "@babel/types": "^7.17.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", + "dev": true, + "requires": { + "@babel/types": "^7.17.0" + } + }, + "@babel/parser": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz", + "integrity": "sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==", + "dev": true + }, + "@babel/traverse": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.9.tgz", + "integrity": "sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.9", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.9", + "@babel/types": "^7.17.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + } } }, "@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.9.tgz", + "integrity": "sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ==", "dev": true, "requires": { "@babel/types": "^7.17.0", @@ -228,27 +318,27 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", "dev": true, "requires": { - "@babel/compat-data": "^7.16.4", + "@babel/compat-data": "^7.17.7", "@babel/helper-validator-option": "^7.16.7", "browserslist": "^4.17.5", "semver": "^6.3.0" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", - "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.9.tgz", + "integrity": "sha512-kUjip3gruz6AJKOq5i3nC6CoCEEF/oHH3cp6tOZhB+IyyyPyW0g1Gfsxn3mkk6S08pIA2y8GQh609v9G/5sHVQ==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-member-expression-to-functions": "^7.17.7", "@babel/helper-optimise-call-expression": "^7.16.7", "@babel/helper-replace-supers": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7" @@ -299,23 +389,13 @@ } }, "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" } }, "@babel/helper-hoist-variables": { @@ -328,12 +408,12 @@ } }, "@babel/helper-member-expression-to-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", - "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", + "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", "dev": true, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" } }, "@babel/helper-module-imports": { @@ -346,14 +426,14 @@ } }, "@babel/helper-module-transforms": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", - "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", @@ -401,12 +481,12 @@ } }, "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", "dev": true, "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -452,14 +532,61 @@ } }, "@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz", + "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==", "dev": true, "requires": { "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.0", + "@babel/traverse": "^7.17.9", "@babel/types": "^7.17.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.9.tgz", + "integrity": "sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ==", + "dev": true, + "requires": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", + "dev": true, + "requires": { + "@babel/template": "^7.16.7", + "@babel/types": "^7.17.0" + } + }, + "@babel/parser": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz", + "integrity": "sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==", + "dev": true + }, + "@babel/traverse": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.9.tgz", + "integrity": "sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.9", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.9", + "@babel/types": "^7.17.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + } } }, "@babel/highlight": { @@ -856,9 +983,9 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", - "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz", + "integrity": "sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" @@ -943,25 +1070,25 @@ } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.9.tgz", + "integrity": "sha512-2TBFd/r2I6VlYn0YRTz2JdazS+FoUuQ2rIFHoAxtyP/0G3D82SBLaRq9rnUkpqlLg03Byfl/+M32mpxjO6KaPw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-module-transforms": "^7.17.7", "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", - "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.17.8.tgz", + "integrity": "sha512-39reIkMTUVagzgA5x88zDYXPCMT6lcaRKs1+S9K6NKBPErbgO/w/kP8GlNQTC87b412ZTlmNgr3k2JrWgHH+Bw==", "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-module-transforms": "^7.17.7", "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "babel-plugin-dynamic-import-node": "^2.3.3" @@ -1065,12 +1192,12 @@ } }, "@babel/plugin-transform-regenerator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", - "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.17.9.tgz", + "integrity": "sha512-Lc2TfbxR1HOyn/c6b4Y/b6NHoTb67n/IoWLxTu4kC7h4KQnWlhCq2S8Tx0t2SVvv5Uu87Hs+6JEJ5kt2tYGylQ==", "dev": true, "requires": { - "regenerator-transform": "^0.14.2" + "regenerator-transform": "^0.15.0" } }, "@babel/plugin-transform-reserved-words": { @@ -1276,21 +1403,29 @@ } }, "@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.9.tgz", + "integrity": "sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==", "dev": true, "requires": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", + "@babel/generator": "^7.17.9", "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", + "@babel/parser": "^7.17.9", "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" + }, + "dependencies": { + "@babel/parser": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz", + "integrity": "sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==", + "dev": true + } } }, "@babel/types": { @@ -1989,21 +2124,21 @@ "integrity": "sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ==" }, "axios": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz", - "integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==", + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", "requires": { "follow-redirects": "^1.14.8" } }, "babel-loader": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", - "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.4.tgz", + "integrity": "sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A==", "dev": true, "requires": { "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", + "loader-utils": "^2.0.0", "make-dir": "^3.1.0", "schema-utils": "^2.6.5" } @@ -2651,9 +2786,9 @@ "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" }, "css-loader": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.0.tgz", - "integrity": "sha512-S7HCfCiDHLA+VXKqdZwyRZgoO0R9BnKDnVIoHMq5grl3N86zAu7MB+FBWHr5xOJC8SmvpTLha/2NpfFkFEN/ig==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", "dev": true, "requires": { "icss-utils": "^5.1.0", @@ -2667,12 +2802,12 @@ }, "dependencies": { "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz", + "integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==", "dev": true, "requires": { - "lru-cache": "^6.0.0" + "lru-cache": "^7.4.0" } } } @@ -3605,6 +3740,14 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, + "i18next": { + "version": "21.6.15", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.15.tgz", + "integrity": "sha512-uMFyw5HGK9KSJ7ok/WUJfeNQj53VOR4NoITtErffWen2v2SjJfpCls7tKTLF1nTCdKRePY4lCoZMLKhRSj/1GA==", + "requires": { + "@babel/runtime": "^7.17.2" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3918,13 +4061,10 @@ } }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true }, "kind-of": { "version": "6.0.3", @@ -3999,25 +4139,14 @@ "dev": true }, "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", "dev": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } + "json5": "^2.1.2" } }, "locate-path": { @@ -4078,13 +4207,10 @@ } }, "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz", + "integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==", + "dev": true }, "make-dir": { "version": "3.1.0", @@ -4271,9 +4397,9 @@ "dev": true }, "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz", + "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==", "dev": true }, "needle": { @@ -4656,9 +4782,9 @@ } }, "postcss": { - "version": "8.4.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.8.tgz", - "integrity": "sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==", + "version": "8.4.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz", + "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==", "dev": true, "requires": { "nanoid": "^3.3.1", @@ -4702,9 +4828,9 @@ } }, "postcss-selector-parser": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", - "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -5421,9 +5547,9 @@ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", "dev": true, "requires": { "@babel/runtime": "^7.8.4" @@ -6814,12 +6940,6 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true } } } diff --git a/web/package.json b/web/package.json index 3e03a6d..0b18615 100644 --- a/web/package.json +++ b/web/package.json @@ -14,9 +14,10 @@ "@ant-design/pro-layout": "^6.23.0", "@ant-design/pro-table": "^2.45.0", "antd": "^4.16.8", - "axios": "latest", + "axios": "^0.26.1", "crypto-js": "^4.1.1", "dayjs": "^1.10.6", + "i18next": "^21.6.15", "lodash": "^4.17.21", "qs": "^6.10.3", "react": "^17.0.2", @@ -29,19 +30,19 @@ "xterm-addon-web-links": "^0.5.1" }, "devDependencies": { - "@babel/core": "latest", - "@babel/preset-env": "latest", - "@babel/preset-react": "latest", + "@babel/core": "^7.17.9", + "@babel/preset-env": "^7.16.11", + "@babel/preset-react": "^7.16.7", "antd-dayjs-webpack-plugin": "^1.0.6", - "babel-loader": "latest", + "babel-loader": "^8.2.4", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^10.2.4", - "css-loader": "latest", + "css-loader": "^6.7.1", "html-webpack-plugin": "^5.5.0", "less": "^4.1.1", "less-loader": "^10.0.1", "react-text-loop": "^2.3.0", - "style-loader": "latest", + "style-loader": "^3.3.1", "uglifyjs-webpack-plugin": "^2.2.0", "webpack": "^5.18.0", "webpack-cli": "^4.4.0", diff --git a/web/src/components/browser.css b/web/src/components/explorer.css similarity index 100% rename from web/src/components/browser.css rename to web/src/components/explorer.css diff --git a/web/src/components/browser.js b/web/src/components/explorer.js similarity index 88% rename from web/src/components/browser.js rename to web/src/components/explorer.js index e6daadf..5579c1e 100644 --- a/web/src/components/browser.js +++ b/web/src/components/explorer.js @@ -2,8 +2,9 @@ import React, {useEffect, useRef, useState} from 'react'; import {message, Modal, Popconfirm} from "antd"; import ProTable from '@ant-design/pro-table'; import {formatSize, post, request, waitTime} from "../utils/utils"; -import './browser.css'; import dayjs from "dayjs"; +import i18n from "../locale/locale"; +import './explorer.css'; function FileBrowser(props) { const [path, setPath] = useState(`/`); @@ -11,14 +12,14 @@ function FileBrowser(props) { const columns = [ { key: 'Name', - title: 'Name', + title: i18n.t('fileName'), dataIndex: 'name', ellipsis: true, width: 180 }, { key: 'Size', - title: 'Size', + title: i18n.t('fileSize'), dataIndex: 'size', ellipsis: true, width: 60, @@ -26,16 +27,16 @@ function FileBrowser(props) { }, { key: 'Time', - title: 'Time Modified', + title: i18n.t('modifyTime'), dataIndex: 'time', ellipsis: true, width: 100, - renderText: (ts, file) => file.type === 0 ? dayjs.unix(ts).format('YYYY/MM/DD HH:mm') : '-' + renderText: (ts, file) => file.type === 0 ? dayjs.unix(ts).format(i18n.t('dateTimeFormat')) : '-' }, { key: 'Option', width: 120, - title: '操作', + title: '', dataIndex: 'name', valueType: 'option', ellipsis: true, @@ -59,10 +60,14 @@ function FileBrowser(props) { let remove = ( - 删除 + {i18n.t('delete')} ); switch (file.type) { @@ -71,7 +76,7 @@ function FileBrowser(props) { 下载, + >{i18n.t('download')}, remove, ]; case 1: @@ -127,10 +132,10 @@ function FileBrowser(props) { } function removeFile(file) { - request(`/api/device/file/remove`, {path: path+file, device: props.device}).then(res => { + request(`/api/device/file/remove`, {path: path + file, device: props.device}).then(res => { let data = res.data; if (data.code === 0) { - message.success('文件或目录已被删除'); + message.success(i18n.t('deleteSuccess')); tableRef.current.reload(); } }); @@ -156,7 +161,7 @@ function FileBrowser(props) { return ({ data: data.data.files, success: true, - total: data.data.files.length - (addParentShortcut?1:0) + total: data.data.files.length - (addParentShortcut ? 1 : 0) }); } setPath(getLastPath()); @@ -166,7 +171,7 @@ function FileBrowser(props) { return ( prebuilt} rules={[{ required: true diff --git a/web/src/components/processes.js b/web/src/components/processes.js index df92b11..2d27024 100644 --- a/web/src/components/processes.js +++ b/web/src/components/processes.js @@ -2,13 +2,14 @@ import React, {useEffect, useRef, useState} from 'react'; import {message, Modal, Popconfirm} from "antd"; import ProTable from '@ant-design/pro-table'; import {request, waitTime} from "../utils/utils"; +import i18n from "../locale/locale"; function ProcessMgr(props) { const [loading, setLoading] = useState(false); const columns = [ { key: 'Name', - title: 'Name', + title: i18n.t('procName'), dataIndex: 'name', ellipsis: true, width: 120 @@ -23,7 +24,7 @@ function ProcessMgr(props) { { key: 'Option', width: 40, - title: '操作', + title: '', dataIndex: 'name', valueType: 'option', ellipsis: true, @@ -46,10 +47,10 @@ function ProcessMgr(props) { return [ - 结束 + {i18n.t('killProc')} ]; } @@ -58,7 +59,7 @@ function ProcessMgr(props) { request(`/api/device/process/kill`, {pid: pid, device: props.device}).then(res => { let data = res.data; if (data.code === 0) { - message.success('进程已结束'); + message.success(i18n.t('killProcSuccess')); tableRef.current.reload(); } }); @@ -83,7 +84,7 @@ function ProcessMgr(props) { return ( - ) diff --git a/web/src/components/terminal.js b/web/src/components/terminal.js index 09a14d2..1e62706 100644 --- a/web/src/components/terminal.js +++ b/web/src/components/terminal.js @@ -6,6 +6,8 @@ import {FitAddon} from "xterm-addon-fit"; import debounce from 'lodash/debounce'; import CryptoJS from 'crypto-js'; import "xterm/css/xterm.css"; +import i18n from "../locale/locale"; +import {translate} from "../utils/utils"; function hex2buf(hex) { if (typeof hex !== 'string') { @@ -121,7 +123,7 @@ class TerminalModal extends React.Component { termEv = this.term.onData((e) => { if (!this.conn) { if (e === '\r' || e === ' ') { - this.term.write('\n正在重新连接...\n'); + this.term.write(`\n${i18n.t('reconnecting')}\n`); this.termEv = this.initialize(termEv); } return; @@ -172,20 +174,20 @@ class TerminalModal extends React.Component { return; } if (data?.act === 'warn') { - message.warn(data.msg??'未知错误'); + message.warn(data.msg ? translate(data.msg) : i18n.t('unknownError')); } } } this.ws.onclose = (e) => { if (this.conn) { this.conn = false; - this.term.write('\n连接已断开!\n'); + this.term.write(`\n${i18n.t('disconnected')}\n`); } } this.ws.onerror = (e) => { if (this.conn) { this.conn = false; - this.term.write('\n连接已断开!\n'); + this.term.write(`\n${i18n.t('disconnected')}\n`); } } return termEv; @@ -285,7 +287,7 @@ class TerminalModal extends React.Component { render() { return ( - {props.children} + + {props.children} + ); diff --git a/web/src/global.css b/web/src/global.css index ea622b4..69602c1 100644 --- a/web/src/global.css +++ b/web/src/global.css @@ -1,3 +1,7 @@ +body { + overflow-x: hidden; +} + #root { height: 100%; background-image: url('static/bg.svg'); diff --git a/web/src/index.js b/web/src/index.js index df80207..5eb2c0a 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -5,21 +5,19 @@ import Wrapper from './components/wrapper'; import Err from './pages/404'; import axios from 'axios'; import {message} from 'antd'; -import dayjs from 'dayjs'; +import i18n from "./locale/locale"; import './global.css'; import 'antd/dist/antd.css'; -import 'dayjs/locale/zh-cn'; import Overview from "./pages/overview"; - -dayjs.locale('zh-cn'); +import {translate} from "./utils/utils"; axios.defaults.baseURL = '.'; axios.interceptors.response.use(async (res) => { let data = res.data; if (data.hasOwnProperty('code')) { if (data.code !== 0){ - message.warn(data.msg); + message.warn(translate(data.msg)); } else { // The first request will ask user to provide user/pass. // If set timeout at the beginning, then timeout warning @@ -30,14 +28,14 @@ axios.interceptors.response.use(async (res) => { return Promise.resolve(res); }, (err) => { if (err.code === 'ECONNABORTED') { - message.warn('请求超时'); + message.warn(i18n.t('requestTimeout')); return Promise.resolve(err); } let res = err.response; let data = res.data; if (data.hasOwnProperty('code')) { if (data.code !== 0){ - message.warn(data.msg); + message.warn(translate(data.msg)); } } return Promise.resolve(res); @@ -47,10 +45,7 @@ ReactDOM.render( }/> - } - /> + }/> , document.getElementById('root') diff --git a/web/src/locale/en.json b/web/src/locale/en.json new file mode 100644 index 0000000..ea576ca --- /dev/null +++ b/web/src/locale/en.json @@ -0,0 +1,73 @@ +{ + "requestTimeout": "Request timeout", + "pageNotFound": "Page Not Found", + "unknownError": "Unknown error", + "invalidParameter": "Invalid parameter", + + "hostname": "Hostname", + "username": "Username", + "cpuUsage": "CPU Usage", + "memUsage": "Mem Usage", + "diskUsage": "Disk Usage", + "memTotal": "Memory", + "os": "OS", + "arch": "Arch", + "uptime": "Uptime", + "netStat": "Network IO", + "operations": "Operations", + "hours": "h", + "minutes": "m", + "terminal": "Terminal", + "procMgr": "ProcMgr", + "fileMgr": "Explorer", + "screenshot": "Screenshot", + "lock": "Lock", + "logoff": "Logoff", + "hibernate": "Hibernate", + "suspend": "Suspend", + "restart": "Restart", + "shutdown": "Shutdown", + "offline": "Offline", + "generate": "Generate client", + "requestFailed": "Request failed", + "operationConfirm": "Are you sure to {0} this device?", + "operationSuccess": "Operation executed", + + "fileExplorer": "File Explorer", + "fileName": "Name", + "fileSize": "Size", + "modifyTime": "Modify Time", + "file": "file", + "folder": "folder", + "delete": "Delete", + "download": "Download", + "deleteConfirm": "Are you sure to delete this {0}?", + "deleteSuccess": "File or folder deleted", + "dateTimeFormat": "MMM D, YYYY h:mm A", + + "host": "Host", + "port": "Port", + "path": "Path", + "osArch": "OS / Arch", + + "processManager": "Process Manager", + "procName": "Process", + "killProc": "Kill", + "confirmKillProc": "Are you sure to kill this process?", + "killProcSuccess": "Process killed", + + "reconnecting": "Reconnecting...", + "disconnected": "Session disconnected", + + "terminalSessionCreationFailed": "Failed to create terminal session", + "deviceNotExists": "Device not exists or not online", + "responseTimeout": "Response timeout", + "terminalSessionClosed": "Terminal session closed", + "osOrArchNotPrebuilt": "The OS or Arch is not prebuilt", + "configGenerateFailed": "Failed to generate client config", + "tooLargeConfig": "Config is too large", + "fileUploadFailed": "Failed to upload file", + "screenshotFailed": "Failed to take screenshot", + "screenshotObtainFailed": "Failed to obtain screenshot", + "invalidFileRange": "Invalid file range" +} \ No newline at end of file diff --git a/web/src/locale/locale.js b/web/src/locale/locale.js new file mode 100644 index 0000000..0a6bbd4 --- /dev/null +++ b/web/src/locale/locale.js @@ -0,0 +1,29 @@ +import i18n from 'i18next'; + +const locales = { + 'en': 'en', + 'en-US': 'en', + 'zh-CN': 'zh-CN', +}; +const lang = navigator.language && navigator.language.length ? navigator.language : 'en'; + +let resources = {}; +for (const locale in locales) { + resources[locale] = { + translation: require(`./${locales[locale]}.json`), + }; +} + +i18n.init({ + lng: lang, + fallbackLng: 'en', + initImmediate: false, + resources +}); + +function getLang() { + return lang; +} + +export { getLang }; +export default i18n; \ No newline at end of file diff --git a/web/src/locale/zh-CN.json b/web/src/locale/zh-CN.json new file mode 100644 index 0000000..c1b0636 --- /dev/null +++ b/web/src/locale/zh-CN.json @@ -0,0 +1,73 @@ +{ + "requestTimeout": "请求超时", + "pageNotFound": "未找到该页面", + "unknownError": "未知错误", + "invalidParameter": "参数无效", + + "hostname": "主机名", + "username": "用户名", + "cpuUsage": "CPU使用率", + "memUsage": "内存使用率", + "diskUsage": "磁盘使用率", + "memTotal": "内存大小", + "os": "操作系统", + "arch": "架构", + "uptime": "运行时间", + "netStat": "网络状态", + "operations": "操作", + "hours": "小时", + "minutes": "分钟", + "terminal": "终端", + "procMgr": "进程", + "fileMgr": "文件", + "screenshot": "截屏", + "lock": "锁屏", + "logoff": "注销", + "hibernate": "休眠", + "suspend": "睡眠", + "restart": "重启", + "shutdown": "关机", + "offline": "离线", + "generate": "生成客户端", + "requestFailed": "请求服务器失败", + "operationConfirm": "确定要{0}该设备吗?", + "operationSuccess": "操作已执行", + + "fileExplorer": "文件管理器", + "fileName": "文件名", + "fileSize": "大小", + "modifyTime": "修改时间", + "file": "文件", + "folder": "文件夹", + "delete": "删除", + "download": "下载", + "deleteConfirm": "确定要删除该{0}吗?", + "deleteSuccess": "文件或目录已被删除", + "dateTimeFormat": "YYYY/MM/DD HH:mm", + + "host": "主机", + "port": "端口", + "path": "路径", + "osArch": "操作系统/架构", + + "processManager": "进程管理器", + "procName": "进程名", + "killProc": "结束", + "confirmKillProc": "确定要结束该进程吗?", + "killProcSuccess": "进程已结束", + + "reconnecting": "正在重新连接...", + "disconnected": "连接已断开", + + "terminalSessionCreationFailed": "终端会话创建失败", + "deviceNotExists": "设备不存在或已离线", + "responseTimeout": "响应超时", + "terminalSessionClosed": "终端会话已关闭", + "osOrArchNotPrebuilt": "该操作系统或架构的客户端未预编译", + "configGenerateFailed": "配置文件生成失败", + "tooLargeConfig": "配置文件过大", + "fileUploadFailed": "文件上传失败", + "screenshotFailed": "截屏失败", + "screenshotObtainFailed": "截屏读取失败", + "invalidFileRange": "文件范围错误" +} \ No newline at end of file diff --git a/web/src/pages/404.js b/web/src/pages/404.js index 03d493a..07729fb 100644 --- a/web/src/pages/404.js +++ b/web/src/pages/404.js @@ -1,4 +1,5 @@ import React from 'react'; +import i18n from "../locale/locale"; export default function () { // setTimeout(()=>{ @@ -7,9 +8,7 @@ export default function () { return (

- Page Not Found. -
- 未找到该页面。 + {i18n.t('pageNotFound')}

); } \ No newline at end of file diff --git a/web/src/pages/overview.js b/web/src/pages/overview.js index 7ebd665..f23575d 100644 --- a/web/src/pages/overview.js +++ b/web/src/pages/overview.js @@ -1,12 +1,13 @@ import React, {useEffect, useRef, useState} from 'react'; import ProTable, {TableDropdown} from '@ant-design/pro-table'; import {Button, Image, message, Modal, Progress} from 'antd'; -import {formatSize, request, tsToTime, waitTime} from "../utils/utils"; +import {formatSize, request, translate, tsToTime, waitTime} from "../utils/utils"; import Terminal from "../components/terminal"; import Processes from "../components/processes"; import Generate from "../components/generate"; -import Browser from "../components/browser"; +import Explorer from "../components/explorer"; import {QuestionCircleOutlined} from "@ant-design/icons"; +import i18n from "../locale/locale"; import defaultColumnsState from "../config/columnsState.json"; @@ -15,7 +16,7 @@ console.log("%c By XZB %c https://github.com/XZB-1248/Spark", 'font-family:"Helv function overview(props) { const [procMgr, setProcMgr] = useState(false); - const [browser, setBrowser] = useState(false); + const [explorer, setExplorer] = useState(false); const [generate, setGenerate] = useState(false); const [terminal, setTerminal] = useState(false); const [screenBlob, setScreenBlob] = useState(''); @@ -26,14 +27,14 @@ function overview(props) { const columns = [ { key: 'hostname', - title: 'Hostname', + title: i18n.t('hostname'), dataIndex: 'hostname', ellipsis: true, width: 100 }, { key: 'username', - title: 'Username', + title: i18n.t('username'), dataIndex: 'username', ellipsis: true, width: 90 @@ -48,7 +49,7 @@ function overview(props) { }, { key: 'cpu_usage', - title: 'CPU Usage', + title: i18n.t('cpuUsage'), dataIndex: 'cpu_usage', ellipsis: true, render: (_, v) => , @@ -56,7 +57,7 @@ function overview(props) { }, { key: 'mem_usage', - title: 'Mem Usage', + title: i18n.t('memUsage'), dataIndex: 'mem_usage', ellipsis: true, render: (_, v) => , @@ -64,7 +65,7 @@ function overview(props) { }, { key: 'disk_usage', - title: 'Disk Usage', + title: i18n.t('diskUsage'), dataIndex: 'disk_usage', ellipsis: true, render: (_, v) => , @@ -72,7 +73,7 @@ function overview(props) { }, { key: 'mem_total', - title: 'Mem', + title: i18n.t('memTotal'), dataIndex: 'mem_total', ellipsis: true, renderText: formatSize, @@ -80,14 +81,14 @@ function overview(props) { }, { key: 'os', - title: 'OS', + title: i18n.t('os'), dataIndex: 'os', ellipsis: true, width: 80 }, { key: 'arch', - title: 'Arch', + title: i18n.t('arch'), dataIndex: 'arch', ellipsis: true, width: 70 @@ -115,7 +116,7 @@ function overview(props) { }, { key: 'uptime', - title: 'Uptime', + title: i18n.t('uptime'), dataIndex: 'uptime', ellipsis: true, renderText: tsToTime, @@ -123,17 +124,17 @@ function overview(props) { }, { key: 'net_stat', - title: 'Network IO', + title: i18n.t('netStat'), ellipsis: true, renderText: (_, v) => renderNetworkIO(v), width: 170 }, { key: 'option', - title: '操作', + title: i18n.t('operations'), dataIndex: 'id', valueType: 'option', - ellipsis: true, + ellipsis: false, render: (_, device) => renderOperation(device), width: 170 }, @@ -147,13 +148,13 @@ function overview(props) { useEffect(() => { // Auto update is only available when all modal are closed. - if (!procMgr && !browser && !generate && !terminal) { + if (!procMgr && !explorer && !generate && !terminal) { let id = setInterval(getData, 3000); return () => { clearInterval(id); }; } - }, [procMgr, browser, generate, terminal]); + }, [procMgr, explorer, generate, terminal]); function getInitColumnsState() { let data = localStorage.getItem(`columnsState`); @@ -193,24 +194,24 @@ function overview(props) { function renderOperation(device) { return [ - 终端, - 进程, - { - setBrowser(device.id); + {i18n.t('terminal')}, + {i18n.t('procMgr')}, + { + setExplorer(device.id); setIsWindows(device.os === 'windows'); - }}>文件, + }}>{i18n.t('fileMgr')}, callDevice(key, device.id)} menus={[ - {key: 'screenshot', name: '截屏'}, - {key: 'lock', name: '锁屏'}, - {key: 'logoff', name: '注销'}, - {key: 'hibernate', name: '休眠'}, - {key: 'suspend', name: '睡眠'}, - {key: 'restart', name: '重启'}, - {key: 'shutdown', name: '关机'}, - {key: 'offline', name: '离线'}, + {key: 'screenshot', name: i18n.t('screenshot')}, + {key: 'lock', name: i18n.t('lock')}, + {key: 'logoff', name: i18n.t('logoff')}, + {key: 'hibernate', name: i18n.t('hibernate')}, + {key: 'suspend', name: i18n.t('suspend')}, + {key: 'restart', name: i18n.t('restart')}, + {key: 'shutdown', name: i18n.t('shutdown')}, + {key: 'offline', name: i18n.t('offline')}, ]} />, ] @@ -228,7 +229,7 @@ function overview(props) { data = JSON.parse(str); } catch (e) { } - message.warn(data.msg ?? '请求服务器失败') + message.warn(data.msg ? translate(data.msg) : i18n.t('requestFailed')); }); } else { if (screenBlob.length > 0) { @@ -239,28 +240,14 @@ function overview(props) { }); return; } - let menus = { - lock: '锁屏', - logoff: '注销', - hibernate: '休眠', - suspend: '睡眠', - restart: '重启', - shutdown: '关机', - offline: '离线', - }; - if (!menus.hasOwnProperty(act)) { - return; - } Modal.confirm({ - title: `确定要${menus[act]}该设备吗?`, + title: i18n.t('operationConfirm').replace('{0}', i18n.t(act).toUpperCase()), icon: , - okText: '确定', - cancelText: '取消', onOk() { request('/api/device/' + act, {device: device}).then(res => { let data = res.data; if (data.code === 0) { - message.success('操作已执行'); + message.success(i18n.t('operationSuccess')); tableRef.current.reload(); } }); @@ -270,7 +257,7 @@ function overview(props) { function toolBar() { return ( - + ) } @@ -336,11 +323,11 @@ function overview(props) { visible={generate} onVisibleChange={setGenerate} /> - { + return i18n.t(key); + }); +} + +export {post, request, waitTime, formatSize, tsToTime, translate}; \ No newline at end of file