From fafa042ee9d322bed4e7217b43373bf6464f95ae Mon Sep 17 00:00:00 2001 From: ssongliu Date: Thu, 25 Jul 2024 14:43:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20core=20=E5=A2=9E=E5=8A=A0=20xpack=20?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 + agent/init/router/router.go | 2 +- agent/server/server.go | 16 +- cmd/server/conf/app.yaml | 2 +- cmd/server/{conf => }/qqwry/qqwey.go | 0 cmd/server/{conf => }/qqwry/qqwry.dat | Bin core/app/api/v1/auth.go | 24 +-- core/app/dto/{common_res.go => common.go} | 10 ++ core/app/dto/common_req.go | 11 -- core/app/repo/common.go | 9 +- core/app/service/entry.go | 2 +- core/constant/errs.go | 8 +- core/i18n/lang/en.yaml | 1 + core/i18n/lang/zh-Hant.yaml | 1 + core/i18n/lang/zh.yaml | 1 + core/init/migration/migrations/init.go | 2 +- core/init/router/router.go | 39 +---- core/middleware/proxy.go | 40 +++-- core/router/entry.go | 2 + core/server/server.go | 1 + core/utils/qqwry/qqwry.go | 165 ++++++++++++++++++ core/utils/ssh/ssh.go | 142 +++++++++++++++ frontend/.env.development | 2 +- frontend/.env.production | 2 +- frontend/src/api/modules/auth.ts | 14 +- frontend/src/api/modules/setting.ts | 2 +- frontend/src/components/group/index.vue | 4 +- frontend/src/lang/modules/en.ts | 3 +- frontend/src/lang/modules/tw.ts | 3 +- frontend/src/lang/modules/zh.ts | 3 +- .../src/views/host/terminal/host/index.vue | 2 +- .../views/host/terminal/terminal/index.vue | 2 +- .../src/views/toolbox/device/ntp/index.vue | 2 +- .../website/runtime/node/operate/index.vue | 2 +- .../website/runtime/php/create/index.vue | 2 +- frontend/vite.config.ts | 2 +- 36 files changed, 411 insertions(+), 116 deletions(-) rename cmd/server/{conf => }/qqwry/qqwey.go (100%) rename cmd/server/{conf => }/qqwry/qqwry.dat (100%) rename core/app/dto/{common_res.go => common.go} (58%) delete mode 100644 core/app/dto/common_req.go create mode 100644 core/utils/qqwry/qqwry.go create mode 100644 core/utils/ssh/ssh.go diff --git a/.gitignore b/.gitignore index 0bca9eb9c..d2308dedc 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ build/1panel .vscode *.project *.factorypath +__debug* # IntelliJ IDEA .idea/* @@ -43,6 +44,9 @@ agent/xpack agent/router/entry_xpack.go agent/server/init_xpack.go agent/utils/xpack/xpack_xpack.go +core/xpack +core/router/entry_xpack.go +core/server/init_xpack.go .history/ dist/ diff --git a/agent/init/router/router.go b/agent/init/router/router.go index 10551cd54..a8803aab7 100644 --- a/agent/init/router/router.go +++ b/agent/init/router/router.go @@ -31,7 +31,7 @@ func Routers() *gin.Engine { PublicGroup.Use(gzip.Gzip(gzip.DefaultCompression)) setWebStatic(PublicGroup) } - PrivateGroup := Router.Group("/api/v1") + PrivateGroup := Router.Group("/api/v2") PrivateGroup.Use(middleware.GlobalLoading()) for _, router := range rou.RouterGroupApp { router.InitRouter(PrivateGroup) diff --git a/agent/server/server.go b/agent/server/server.go index dcb247f12..e4b1b075a 100644 --- a/agent/server/server.go +++ b/agent/server/server.go @@ -3,6 +3,7 @@ package server import ( "net" "net/http" + "os" "github.com/1Panel-dev/1Panel/agent/cron" "github.com/1Panel-dev/1Panel/agent/i18n" @@ -38,22 +39,11 @@ func Start() { server := &http.Server{ Handler: rootRouter, } - //ln, err := net.Listen("tcp4", "0.0.0.0:9998") - //if err != nil { - // panic(err) - //} - //type tcpKeepAliveListener struct { - // *net.TCPListener - //} - // - //global.LOG.Info("listen at http://0.0.0.0:9998") - //if err := server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}); err != nil { - // panic(err) - //} + _ = os.Remove("/tmp/agent.sock") listener, err := net.Listen("unix", "/tmp/agent.sock") if err != nil { panic(err) } - server.Serve(listener) + _ = server.Serve(listener) } diff --git a/cmd/server/conf/app.yaml b/cmd/server/conf/app.yaml index 61c29db47..de1b82a88 100644 --- a/cmd/server/conf/app.yaml +++ b/cmd/server/conf/app.yaml @@ -1,6 +1,6 @@ system: db_core_file: 1Panel_Core.db - db_file: 1Panel_Core.db + db_file: 1Panel.db base_dir: /opt mode: dev repo_url: https://resource.fit2cloud.com/1panel/package diff --git a/cmd/server/conf/qqwry/qqwey.go b/cmd/server/qqwry/qqwey.go similarity index 100% rename from cmd/server/conf/qqwry/qqwey.go rename to cmd/server/qqwry/qqwey.go diff --git a/cmd/server/conf/qqwry/qqwry.dat b/cmd/server/qqwry/qqwry.dat similarity index 100% rename from cmd/server/conf/qqwry/qqwry.dat rename to cmd/server/qqwry/qqwry.dat diff --git a/core/app/api/v1/auth.go b/core/app/api/v1/auth.go index e9e7a7588..9f4c2ba38 100644 --- a/core/app/api/v1/auth.go +++ b/core/app/api/v1/auth.go @@ -8,9 +8,9 @@ import ( "github.com/1Panel-dev/1Panel/core/app/model" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" - - // "github.com/1Panel-dev/1Panel/core/middleware" + "github.com/1Panel-dev/1Panel/core/middleware" "github.com/1Panel-dev/1Panel/core/utils/captcha" + "github.com/1Panel-dev/1Panel/core/utils/qqwry" "github.com/gin-gonic/gin" ) @@ -124,10 +124,10 @@ func (b *BaseApi) CheckIsSafety(c *gin.Context) { return } if status == "unpass" { - // if middleware.Get.LoadErrCode("err-entrance") != 200 { - // helper.ErrResponse(c, middleware.LoadErrCode("err-entrance")) - // return - // } + if middleware.LoadErrCode("err-entrance") != 200 { + helper.ErrResponse(c, middleware.LoadErrCode("err-entrance")) + return + } helper.ErrorWithDetail(c, constant.CodeErrEntrance, constant.ErrTypeInternalServer, nil) return } @@ -175,12 +175,12 @@ func saveLoginLogs(c *gin.Context, err error) { logs.Status = constant.StatusSuccess } logs.IP = c.ClientIP() - // qqWry, err := qqwry.NewQQwry() - // if err != nil { - // global.LOG.Errorf("load qqwry datas failed: %s", err) - // } - // res := qqWry.Find(logs.IP) + qqWry, err := qqwry.NewQQwry() + if err != nil { + global.LOG.Errorf("load qqwry datas failed: %s", err) + } + res := qqWry.Find(logs.IP) logs.Agent = c.GetHeader("User-Agent") - // logs.Address = res.Area + logs.Address = res.Area _ = logService.CreateLoginLog(logs) } diff --git a/core/app/dto/common_res.go b/core/app/dto/common.go similarity index 58% rename from core/app/dto/common_res.go rename to core/app/dto/common.go index 5c7c11193..cca92a13d 100644 --- a/core/app/dto/common_res.go +++ b/core/app/dto/common.go @@ -1,5 +1,15 @@ package dto +type SearchWithPage struct { + PageInfo + Info string `json:"info"` +} + +type PageInfo struct { + Page int `json:"page" validate:"required,number"` + PageSize int `json:"pageSize" validate:"required,number"` +} + type PageResult struct { Total int64 `json:"total"` Items interface{} `json:"items"` diff --git a/core/app/dto/common_req.go b/core/app/dto/common_req.go deleted file mode 100644 index b7804ab26..000000000 --- a/core/app/dto/common_req.go +++ /dev/null @@ -1,11 +0,0 @@ -package dto - -type SearchWithPage struct { - PageInfo - Info string `json:"info"` -} - -type PageInfo struct { - Page int `json:"page" validate:"required,number"` - PageSize int `json:"pageSize" validate:"required,number"` -} diff --git a/core/app/repo/common.go b/core/app/repo/common.go index 4610cd1f4..1d054b053 100644 --- a/core/app/repo/common.go +++ b/core/app/repo/common.go @@ -7,14 +7,21 @@ import ( type DBOption func(*gorm.DB) *gorm.DB type ICommonRepo interface { + WithByID(id uint) DBOption WithOrderBy(orderStr string) DBOption } type CommonRepo struct{} -func NewCommonRepo() ICommonRepo { +func NewICommonRepo() ICommonRepo { return &CommonRepo{} } + +func (c *CommonRepo) WithByID(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id = ?", id) + } +} func (c *CommonRepo) WithOrderBy(orderStr string) DBOption { return func(g *gorm.DB) *gorm.DB { return g.Order(orderStr) diff --git a/core/app/service/entry.go b/core/app/service/entry.go index f4987166d..834443f26 100644 --- a/core/app/service/entry.go +++ b/core/app/service/entry.go @@ -3,7 +3,7 @@ package service import "github.com/1Panel-dev/1Panel/core/app/repo" var ( - commonRepo = repo.NewCommonRepo() + commonRepo = repo.NewICommonRepo() settingRepo = repo.NewISettingRepo() logRepo = repo.NewILogRepo() ) diff --git a/core/constant/errs.go b/core/constant/errs.go index f829c7f33..10bc0b278 100644 --- a/core/constant/errs.go +++ b/core/constant/errs.go @@ -31,9 +31,11 @@ var ( ErrInitialPassword = errors.New("ErrInitialPassword") ErrInvalidParams = errors.New("ErrInvalidParams") - ErrTokenParse = errors.New("ErrTokenParse") - ErrPortInUsed = "ErrPortInUsed" - ErrCmdTimeout = "ErrCmdTimeout" + ErrTokenParse = errors.New("ErrTokenParse") + ErrStructTransform = errors.New("ErrStructTransform") + ErrPortInUsed = "ErrPortInUsed" + ErrCmdTimeout = "ErrCmdTimeout" + ErrGroupIsUsed = "ErrGroupIsUsed" ) // api diff --git a/core/i18n/lang/en.yaml b/core/i18n/lang/en.yaml index f5cfc4b39..b23679396 100644 --- a/core/i18n/lang/en.yaml +++ b/core/i18n/lang/en.yaml @@ -13,6 +13,7 @@ ErrNotSupportType: "The system does not support the current type: {{ .detail }}" ErrNameIsExist: "Name is already exist" ErrDemoEnvironment: "Demo server, prohibit this operation!" ErrEntrance: "Security entrance information error. Please check and try again!" +ErrGroupIsUsed: "The group is in use and cannot be deleted" #app ErrPortInUsed: "{{ .detail }} port already in use" diff --git a/core/i18n/lang/zh-Hant.yaml b/core/i18n/lang/zh-Hant.yaml index 6c9d5019f..7c876b4f1 100644 --- a/core/i18n/lang/zh-Hant.yaml +++ b/core/i18n/lang/zh-Hant.yaml @@ -13,6 +13,7 @@ ErrNotSupportType: "系統暫不支持當前類型: {{ .detail }}" ErrNameIsExist: "名稱已存在" ErrDemoEnvironment: "演示伺服器,禁止此操作!" ErrEntrance: "安全入口信息錯誤,請檢查後重試!" +ErrGroupIsUsed: "分組正在使用中,無法刪除" #app ErrPortInUsed: "{{ .detail }} 端口已被佔用!" diff --git a/core/i18n/lang/zh.yaml b/core/i18n/lang/zh.yaml index 19383dd9b..8c25501c8 100644 --- a/core/i18n/lang/zh.yaml +++ b/core/i18n/lang/zh.yaml @@ -13,6 +13,7 @@ ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}" ErrDemoEnvironment: "演示服务器,禁止此操作!" ErrCmdTimeout: "命令执行超时!" ErrEntrance: "安全入口信息错误,请检查后重试!" +ErrGroupIsUsed: "分组正在使用中,无法删除" #app ErrPortInUsed: "{{ .detail }} 端口已被占用!" diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go index 4e3bed392..67ea03ad0 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -82,7 +82,7 @@ var InitSetting = &gormigrate.Migration{ if err := tx.Create(&model.Setting{Key: "PrsoxyPasswdKeep", Value: ""}).Error; err != nil { return err } - if err := tx.Create(&model.Setting{Key: "XpackHideMenu", Value: "{\"id\":\"1\",\"label\":\"/xpack\",\"isCheck\":true,\"title\":\"xpack.menu\",\"children\":[{\"id\":\"2\",\"title\":\"xpack.waf.name\",\"path\":\"/xpack/waf/dashboard\",\"label\":\"Dashboard\",\"isCheck\":true},{\"id\":\"3\",\"title\":\"xpack.tamper.tamper\",\"path\":\"/xpack/tamper\",\"label\":\"Tamper\",\"isCheck\":true},{\"id\":\"4\",\"title\":\"xpack.gpu.gpu\",\"path\":\"/xpack/gpu\",\"label\":\"GPU\",\"isCheck\":true},{\"id\":\"5\",\"title\":\"xpack.setting.setting\",\"path\":\"/xpack/setting\",\"label\":\"XSetting\",\"isCheck\":true},{\"id\":\"6\",\"title\":\"xpack.monitor.name\",\"path\":\"/xpack/monitor/dashboard\",\"label\":\"MonitorDashboard\",\"isCheck\":true},{\"id\":\"7\",\"title\":\"xpack.multihost.agentManagement\",\"path\":\"/xpack/multihost/manage\",\"label\":\"Multihost\",\"isCheck\":true}]}"}).Error; err != nil { + if err := tx.Create(&model.Setting{Key: "XpackHideMenu", Value: "{\"id\":\"1\",\"label\":\"/xpack\",\"isCheck\":true,\"title\":\"xpack.menu\",\"children\":[{\"id\":\"2\",\"title\":\"xpack.waf.name\",\"path\":\"/xpack/waf/dashboard\",\"label\":\"Dashboard\",\"isCheck\":true},{\"id\":\"3\",\"title\":\"xpack.tamper.tamper\",\"path\":\"/xpack/tamper\",\"label\":\"Tamper\",\"isCheck\":true},{\"id\":\"4\",\"title\":\"xpack.gpu.gpu\",\"path\":\"/xpack/gpu\",\"label\":\"GPU\",\"isCheck\":true},{\"id\":\"5\",\"title\":\"xpack.setting.setting\",\"path\":\"/xpack/setting\",\"label\":\"XSetting\",\"isCheck\":true},{\"id\":\"6\",\"title\":\"xpack.monitor.name\",\"path\":\"/xpack/monitor/dashboard\",\"label\":\"MonitorDashboard\",\"isCheck\":true},{\"id\":\"7\",\"title\":\"xpack.node.nodeManagement\",\"path\":\"/xpack/node\",\"label\":\"Node\",\"isCheck\":true}]}"}).Error; err != nil { return err } diff --git a/core/init/router/router.go b/core/init/router/router.go index 843ef6abc..a3d948425 100644 --- a/core/init/router/router.go +++ b/core/init/router/router.go @@ -1,8 +1,9 @@ package router import ( - "context" "fmt" + "net/http" + "github.com/1Panel-dev/1Panel/cmd/server/docs" "github.com/1Panel-dev/1Panel/cmd/server/web" "github.com/1Panel-dev/1Panel/core/global" @@ -13,11 +14,6 @@ import ( "github.com/gin-gonic/gin" swaggerfiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" - "net" - "net/http" - "net/http/httputil" - "os" - "strings" ) var ( @@ -62,7 +58,7 @@ func Routers() *gin.Engine { PublicGroup.Use(gzip.Gzip(gzip.DefaultCompression)) setWebStatic(PublicGroup) } - PrivateGroup := Router.Group("/api/v1") + PrivateGroup := Router.Group("/api/v2/core") PrivateGroup.Use(middleware.WhiteAllow()) PrivateGroup.Use(middleware.BindDomain()) PrivateGroup.Use(middleware.GlobalLoading()) @@ -70,35 +66,6 @@ func Routers() *gin.Engine { router.InitRouter(PrivateGroup) } - // 使用 unix 代理 - sockPath := "/tmp/agent.sock" - if _, err := os.Stat(sockPath); err != nil { - panic(err) - } - dialUnix := func(proto, addr string) (conn net.Conn, err error) { - return net.Dial("unix", sockPath) - } - transport := &http.Transport{ - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return dialUnix(network, addr) - }, - } - proxy := &httputil.ReverseProxy{ - Director: func(req *http.Request) { - req.URL.Scheme = "http" - req.URL.Host = "unix" - }, - Transport: transport, - } - Router.Use(func(c *gin.Context) { - if strings.HasPrefix(c.Request.URL.Path, "/api") { - proxy.ServeHTTP(c.Writer, c.Request) - c.Abort() - return - } - c.Next() - }) - Router.NoRoute(func(c *gin.Context) { c.Writer.WriteHeader(http.StatusOK) _, _ = c.Writer.Write(web.IndexByte) diff --git a/core/middleware/proxy.go b/core/middleware/proxy.go index 17eb417ca..84510ed1a 100644 --- a/core/middleware/proxy.go +++ b/core/middleware/proxy.go @@ -1,6 +1,11 @@ package middleware import ( + "context" + "net" + "net/http" + "net/http/httputil" + "os" "strings" "github.com/gin-gonic/gin" @@ -8,20 +13,31 @@ import ( func Proxy() gin.HandlerFunc { return func(c *gin.Context) { - if strings.HasPrefix(c.Request.URL.Path, "/api/v1/auth") || - strings.HasPrefix(c.Request.URL.Path, "/api/v1/setting") || - strings.HasPrefix(c.Request.URL.Path, "/api/v1/log") { + if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core") { c.Next() return + } else { + sockPath := "/tmp/agent.sock" + if _, err := os.Stat(sockPath); err != nil { + panic(err) + } + dialUnix := func() (conn net.Conn, err error) { + return net.Dial("unix", sockPath) + } + transport := &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialUnix() + }, + } + proxy := &httputil.ReverseProxy{ + Director: func(req *http.Request) { + req.URL.Scheme = "http" + req.URL.Host = "unix" + }, + Transport: transport, + } + proxy.ServeHTTP(c.Writer, c.Request) + c.Abort() } - //target, err := url.Parse("http://127.0.0.1:9998") - //if err != nil { - // fmt.Printf("Failed to parse target URL: %v", err) - //} - //proxy := httputil.NewSingleHostReverseProxy(target) - //c.Request.Host = target.Host - //c.Request.URL.Scheme = target.Scheme - //c.Request.URL.Host = target.Host - //proxy.ServeHTTP(c.Writer, c.Request) } } diff --git a/core/router/entry.go b/core/router/entry.go index da8fc47ad..49ed25fd1 100644 --- a/core/router/entry.go +++ b/core/router/entry.go @@ -1,3 +1,5 @@ +//go:build !xpack + package router func RouterGroups() []CommonRouter { diff --git a/core/server/server.go b/core/server/server.go index c8a3166ce..b58ce5aa0 100644 --- a/core/server/server.go +++ b/core/server/server.go @@ -36,6 +36,7 @@ func Start() { cache.Init() session.Init() gin.SetMode("debug") + InitOthers() hook.Init() rootRouter := router.Routers() diff --git a/core/utils/qqwry/qqwry.go b/core/utils/qqwry/qqwry.go new file mode 100644 index 000000000..bd2b3d46a --- /dev/null +++ b/core/utils/qqwry/qqwry.go @@ -0,0 +1,165 @@ +package qqwry + +import ( + "encoding/binary" + "net" + "strings" + + "github.com/1Panel-dev/1Panel/cmd/server/qqwry" + "golang.org/x/text/encoding/simplifiedchinese" +) + +const ( + indexLen = 7 + redirectMode1 = 0x01 + redirectMode2 = 0x02 +) + +var IpCommonDictionary []byte + +type QQwry struct { + Data []byte + Offset int64 +} + +func NewQQwry() (*QQwry, error) { + IpCommonDictionary := qqwry.QQwryByte + return &QQwry{Data: IpCommonDictionary}, nil +} + +// readData 从文件中读取数据 +func (q *QQwry) readData(num int, offset ...int64) (rs []byte) { + if len(offset) > 0 { + q.setOffset(offset[0]) + } + nums := int64(num) + end := q.Offset + nums + dataNum := int64(len(q.Data)) + if q.Offset > dataNum { + return nil + } + + if end > dataNum { + end = dataNum + } + rs = q.Data[q.Offset:end] + q.Offset = end + return +} + +// setOffset 设置偏移量 +func (q *QQwry) setOffset(offset int64) { + q.Offset = offset +} + +// Find ip地址查询对应归属地信息 +func (q *QQwry) Find(ip string) (res ResultQQwry) { + res = ResultQQwry{} + res.IP = ip + if strings.Count(ip, ".") != 3 { + return res + } + offset := q.searchIndex(binary.BigEndian.Uint32(net.ParseIP(ip).To4())) + if offset <= 0 { + return + } + + var area []byte + mode := q.readMode(offset + 4) + if mode == redirectMode1 { + countryOffset := q.readUInt24() + mode = q.readMode(countryOffset) + if mode == redirectMode2 { + c := q.readUInt24() + area = q.readString(c) + } else { + area = q.readString(countryOffset) + } + } else if mode == redirectMode2 { + countryOffset := q.readUInt24() + area = q.readString(countryOffset) + } else { + area = q.readString(offset + 4) + } + + enc := simplifiedchinese.GBK.NewDecoder() + res.Area, _ = enc.String(string(area)) + + return +} + +type ResultQQwry struct { + IP string `json:"ip"` + Area string `json:"area"` +} + +// readMode 获取偏移值类型 +func (q *QQwry) readMode(offset uint32) byte { + mode := q.readData(1, int64(offset)) + return mode[0] +} + +// readString 获取字符串 +func (q *QQwry) readString(offset uint32) []byte { + q.setOffset(int64(offset)) + data := make([]byte, 0, 30) + for { + buf := q.readData(1) + if buf[0] == 0 { + break + } + data = append(data, buf[0]) + } + return data +} + +// searchIndex 查找索引位置 +func (q *QQwry) searchIndex(ip uint32) uint32 { + header := q.readData(8, 0) + + start := binary.LittleEndian.Uint32(header[:4]) + end := binary.LittleEndian.Uint32(header[4:]) + + for { + mid := q.getMiddleOffset(start, end) + buf := q.readData(indexLen, int64(mid)) + _ip := binary.LittleEndian.Uint32(buf[:4]) + + if end-start == indexLen { + offset := byteToUInt32(buf[4:]) + buf = q.readData(indexLen) + if ip < binary.LittleEndian.Uint32(buf[:4]) { + return offset + } + return 0 + } + + if _ip > ip { + end = mid + } else if _ip < ip { + start = mid + } else if _ip == ip { + return byteToUInt32(buf[4:]) + } + } +} + +// readUInt24 +func (q *QQwry) readUInt24() uint32 { + buf := q.readData(3) + return byteToUInt32(buf) +} + +// getMiddleOffset +func (q *QQwry) getMiddleOffset(start uint32, end uint32) uint32 { + records := ((end - start) / indexLen) >> 1 + return start + records*indexLen +} + +// byteToUInt32 将 byte 转换为uint32 +func byteToUInt32(data []byte) uint32 { + i := uint32(data[0]) & 0xff + i |= (uint32(data[1]) << 8) & 0xff00 + i |= (uint32(data[2]) << 16) & 0xff0000 + return i +} diff --git a/core/utils/ssh/ssh.go b/core/utils/ssh/ssh.go new file mode 100644 index 000000000..f487182d4 --- /dev/null +++ b/core/utils/ssh/ssh.go @@ -0,0 +1,142 @@ +package ssh + +import ( + "bytes" + "fmt" + "io" + "strings" + "sync" + "time" + + gossh "golang.org/x/crypto/ssh" +) + +type ConnInfo struct { + User string `json:"user"` + Addr string `json:"addr"` + Port int `json:"port"` + AuthMode string `json:"authMode"` + Password string `json:"password"` + PrivateKey []byte `json:"privateKey"` + PassPhrase []byte `json:"passPhrase"` + DialTimeOut time.Duration `json:"dialTimeOut"` + + Client *gossh.Client `json:"client"` + Session *gossh.Session `json:"session"` + LastResult string `json:"lastResult"` +} + +func (c *ConnInfo) NewClient() (*ConnInfo, error) { + if strings.Contains(c.Addr, ":") { + c.Addr = fmt.Sprintf("[%s]", c.Addr) + } + config := &gossh.ClientConfig{} + config.SetDefaults() + addr := fmt.Sprintf("%s:%d", c.Addr, c.Port) + config.User = c.User + if c.AuthMode == "password" { + config.Auth = []gossh.AuthMethod{gossh.Password(c.Password)} + } else { + signer, err := makePrivateKeySigner(c.PrivateKey, c.PassPhrase) + if err != nil { + return nil, err + } + config.Auth = []gossh.AuthMethod{gossh.PublicKeys(signer)} + } + if c.DialTimeOut == 0 { + c.DialTimeOut = 5 * time.Second + } + config.Timeout = c.DialTimeOut + + config.HostKeyCallback = gossh.InsecureIgnoreHostKey() + proto := "tcp" + if strings.Contains(c.Addr, ":") { + proto = "tcp6" + } + client, err := gossh.Dial(proto, addr, config) + if nil != err { + return c, err + } + c.Client = client + return c, nil +} + +func (c *ConnInfo) Run(shell string) (string, error) { + if c.Client == nil { + if _, err := c.NewClient(); err != nil { + return "", err + } + } + session, err := c.Client.NewSession() + if err != nil { + return "", err + } + defer session.Close() + buf, err := session.CombinedOutput(shell) + + c.LastResult = string(buf) + return c.LastResult, err +} + +func (c *ConnInfo) Close() { + _ = c.Client.Close() +} + +type SshConn struct { + StdinPipe io.WriteCloser + ComboOutput *wsBufferWriter + Session *gossh.Session +} + +func (c *ConnInfo) NewSshConn(cols, rows int) (*SshConn, error) { + sshSession, err := c.Client.NewSession() + if err != nil { + return nil, err + } + + stdinP, err := sshSession.StdinPipe() + if err != nil { + return nil, err + } + + comboWriter := new(wsBufferWriter) + sshSession.Stdout = comboWriter + sshSession.Stderr = comboWriter + + modes := gossh.TerminalModes{ + gossh.ECHO: 1, + gossh.TTY_OP_ISPEED: 14400, + gossh.TTY_OP_OSPEED: 14400, + } + if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil { + return nil, err + } + if err := sshSession.Shell(); err != nil { + return nil, err + } + return &SshConn{StdinPipe: stdinP, ComboOutput: comboWriter, Session: sshSession}, nil +} + +func (s *SshConn) Close() { + if s.Session != nil { + s.Session.Close() + } +} + +type wsBufferWriter struct { + buffer bytes.Buffer + mu sync.Mutex +} + +func (w *wsBufferWriter) Write(p []byte) (int, error) { + w.mu.Lock() + defer w.mu.Unlock() + return w.buffer.Write(p) +} + +func makePrivateKeySigner(privateKey []byte, passPhrase []byte) (gossh.Signer, error) { + if len(passPhrase) != 0 { + return gossh.ParsePrivateKeyWithPassphrase(privateKey, passPhrase) + } + return gossh.ParsePrivateKey(privateKey) +} diff --git a/frontend/.env.development b/frontend/.env.development index 023d627b9..50cf7bfce 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -2,7 +2,7 @@ NODE_ENV = 'development' # 本地环境接口地址 -VITE_API_URL = '/api/v1' +VITE_API_URL = '/api/v2' # 是否生成包预览文件 VITE_REPORT = false diff --git a/frontend/.env.production b/frontend/.env.production index 53292f176..bdba33ea3 100644 --- a/frontend/.env.production +++ b/frontend/.env.production @@ -2,7 +2,7 @@ NODE_ENV = "production" # 线上环境接口地址 -VITE_API_URL = '/api/v1' +VITE_API_URL = '/api/v2 # 是否生成包预览文件 VITE_REPORT = true diff --git a/frontend/src/api/modules/auth.ts b/frontend/src/api/modules/auth.ts index 937344d41..1c388dd1c 100644 --- a/frontend/src/api/modules/auth.ts +++ b/frontend/src/api/modules/auth.ts @@ -2,29 +2,29 @@ import { Login } from '@/api/interface/auth'; import http from '@/api'; export const loginApi = (params: Login.ReqLoginForm) => { - return http.post(`/auth/login`, params); + return http.post(`/core/auth/login`, params); }; export const mfaLoginApi = (params: Login.MFALoginForm) => { - return http.post(`/auth/mfalogin`, params); + return http.post(`/core/auth/mfalogin`, params); }; export const getCaptcha = () => { - return http.get(`/auth/captcha`); + return http.get(`/core/auth/captcha`); }; export const logOutApi = () => { - return http.post(`/auth/logout`); + return http.post(`/core/auth/logout`); }; export const checkIsSafety = (code: string) => { - return http.get(`/auth/issafety?code=${code}`); + return http.get(`/core/auth/issafety?code=${code}`); }; export const checkIsDemo = () => { - return http.get('/auth/demo'); + return http.get('/core/auth/demo'); }; export const getLanguage = () => { - return http.get(`/auth/language`); + return http.get(`/core/auth/language`); }; diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts index d9ecd3137..e2847d308 100644 --- a/frontend/src/api/modules/setting.ts +++ b/frontend/src/api/modules/setting.ts @@ -26,7 +26,7 @@ export const unbindLicense = () => { }; export const getSettingInfo = () => { - return http.post(`/settings/search`); + return http.post(`/core/settings/search`); }; export const getSystemAvailable = () => { return http.get(`/settings/search/available`); diff --git a/frontend/src/components/group/index.vue b/frontend/src/components/group/index.vue index a3d622aa4..6b883ab0e 100644 --- a/frontend/src/components/group/index.vue +++ b/frontend/src/components/group/index.vue @@ -18,10 +18,10 @@