mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-18 22:22:59 +08:00
feat: core 增加 xpack 节点管理
This commit is contained in:
parent
cabca70ee5
commit
fafa042ee9
4
.gitignore
vendored
4
.gitignore
vendored
@ -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/
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"`
|
@ -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"`
|
||||
}
|
@ -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)
|
||||
|
@ -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()
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -13,6 +13,7 @@ ErrNotSupportType: "系統暫不支持當前類型: {{ .detail }}"
|
||||
ErrNameIsExist: "名稱已存在"
|
||||
ErrDemoEnvironment: "演示伺服器,禁止此操作!"
|
||||
ErrEntrance: "安全入口信息錯誤,請檢查後重試!"
|
||||
ErrGroupIsUsed: "分組正在使用中,無法刪除"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "{{ .detail }} 端口已被佔用!"
|
||||
|
@ -13,6 +13,7 @@ ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}"
|
||||
ErrDemoEnvironment: "演示服务器,禁止此操作!"
|
||||
ErrCmdTimeout: "命令执行超时!"
|
||||
ErrEntrance: "安全入口信息错误,请检查后重试!"
|
||||
ErrGroupIsUsed: "分组正在使用中,无法删除"
|
||||
|
||||
#app
|
||||
ErrPortInUsed: "{{ .detail }} 端口已被占用!"
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
//go:build !xpack
|
||||
|
||||
package router
|
||||
|
||||
func RouterGroups() []CommonRouter {
|
||||
|
@ -36,6 +36,7 @@ func Start() {
|
||||
cache.Init()
|
||||
session.Init()
|
||||
gin.SetMode("debug")
|
||||
InitOthers()
|
||||
hook.Init()
|
||||
|
||||
rootRouter := router.Routers()
|
||||
|
165
core/utils/qqwry/qqwry.go
Normal file
165
core/utils/qqwry/qqwry.go
Normal file
@ -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
|
||||
}
|
142
core/utils/ssh/ssh.go
Normal file
142
core/utils/ssh/ssh.go
Normal file
@ -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)
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
NODE_ENV = 'development'
|
||||
|
||||
# 本地环境接口地址
|
||||
VITE_API_URL = '/api/v1'
|
||||
VITE_API_URL = '/api/v2'
|
||||
|
||||
# 是否生成包预览文件
|
||||
VITE_REPORT = false
|
||||
|
@ -2,7 +2,7 @@
|
||||
NODE_ENV = "production"
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_API_URL = '/api/v1'
|
||||
VITE_API_URL = '/api/v2
|
||||
|
||||
# 是否生成包预览文件
|
||||
VITE_REPORT = true
|
||||
|
@ -2,29 +2,29 @@ import { Login } from '@/api/interface/auth';
|
||||
import http from '@/api';
|
||||
|
||||
export const loginApi = (params: Login.ReqLoginForm) => {
|
||||
return http.post<Login.ResLogin>(`/auth/login`, params);
|
||||
return http.post<Login.ResLogin>(`/core/auth/login`, params);
|
||||
};
|
||||
|
||||
export const mfaLoginApi = (params: Login.MFALoginForm) => {
|
||||
return http.post<Login.ResLogin>(`/auth/mfalogin`, params);
|
||||
return http.post<Login.ResLogin>(`/core/auth/mfalogin`, params);
|
||||
};
|
||||
|
||||
export const getCaptcha = () => {
|
||||
return http.get<Login.ResCaptcha>(`/auth/captcha`);
|
||||
return http.get<Login.ResCaptcha>(`/core/auth/captcha`);
|
||||
};
|
||||
|
||||
export const logOutApi = () => {
|
||||
return http.post<any>(`/auth/logout`);
|
||||
return http.post<any>(`/core/auth/logout`);
|
||||
};
|
||||
|
||||
export const checkIsSafety = (code: string) => {
|
||||
return http.get<string>(`/auth/issafety?code=${code}`);
|
||||
return http.get<string>(`/core/auth/issafety?code=${code}`);
|
||||
};
|
||||
|
||||
export const checkIsDemo = () => {
|
||||
return http.get<boolean>('/auth/demo');
|
||||
return http.get<boolean>('/core/auth/demo');
|
||||
};
|
||||
|
||||
export const getLanguage = () => {
|
||||
return http.get<string>(`/auth/language`);
|
||||
return http.get<string>(`/core/auth/language`);
|
||||
};
|
||||
|
@ -26,7 +26,7 @@ export const unbindLicense = () => {
|
||||
};
|
||||
|
||||
export const getSettingInfo = () => {
|
||||
return http.post<Setting.SettingInfo>(`/settings/search`);
|
||||
return http.post<Setting.SettingInfo>(`/core/settings/search`);
|
||||
};
|
||||
export const getSystemAvailable = () => {
|
||||
return http.get(`/settings/search/available`);
|
||||
|
@ -18,10 +18,10 @@
|
||||
<template #default="{ row }">
|
||||
<div v-if="!row.edit">
|
||||
<span v-if="row.name === 'default'">
|
||||
{{ $t('website.default') }}
|
||||
{{ $t('commons.table.default') }}
|
||||
</span>
|
||||
<span v-if="row.name !== 'default'">{{ row.name }}</span>
|
||||
<span v-if="row.isDefault">({{ $t('website.default') }})</span>
|
||||
<span v-if="row.isDefault">({{ $t('commons.table.default') }})</span>
|
||||
</div>
|
||||
|
||||
<el-form @submit.prevent ref="groupForm" v-if="row.edit" :model="row">
|
||||
|
@ -88,6 +88,7 @@ const message = {
|
||||
statusWaiting: 'Waiting...',
|
||||
records: 'Records',
|
||||
group: 'Group',
|
||||
default: 'Default',
|
||||
createdAt: 'Creation Time',
|
||||
publishedAt: 'Publish Time',
|
||||
date: 'Date',
|
||||
@ -1928,7 +1929,6 @@ const message = {
|
||||
nginxPer: 'Performance Tuning',
|
||||
neverExpire: 'Never Expire',
|
||||
setDefault: 'Set as default',
|
||||
default: 'Default',
|
||||
deleteHelper: 'Related application status is abnormal, please check',
|
||||
toApp: 'Go to the installed list',
|
||||
cycle: 'Cycle',
|
||||
@ -2334,7 +2334,6 @@ const message = {
|
||||
ustc: 'University of Science and Technology of China',
|
||||
netease: 'Netease',
|
||||
aliyun: 'Alibaba Cloud',
|
||||
default: 'default',
|
||||
tsinghua: 'Tsinghua University',
|
||||
xtomhk: 'XTOM Mirror Station (Hong Kong)',
|
||||
xtom: 'XTOM Mirror Station (Global)',
|
||||
|
@ -87,6 +87,7 @@ const message = {
|
||||
statusWaiting: '進行中...',
|
||||
records: '任務輸出',
|
||||
group: '分組',
|
||||
default: '默認',
|
||||
createdAt: '創建時間',
|
||||
publishedAt: '發布時間',
|
||||
date: '時間',
|
||||
@ -1794,7 +1795,6 @@ const message = {
|
||||
nginxPer: '性能調整',
|
||||
neverExpire: '永不過期',
|
||||
setDefault: '設為默認',
|
||||
default: '默認',
|
||||
deleteHelper: '相關應用狀態不正常,請檢查',
|
||||
toApp: '去已安裝列表',
|
||||
cycle: '周期',
|
||||
@ -2167,7 +2167,6 @@ const message = {
|
||||
ustc: '中國科學技術大學',
|
||||
netease: '網易',
|
||||
aliyun: '阿里雲',
|
||||
default: '默認',
|
||||
tsinghua: '清華大學',
|
||||
xtomhk: 'XTOM 鏡像站(香港)',
|
||||
xtom: 'XTOM 鏡像站(全球)',
|
||||
|
@ -87,6 +87,7 @@ const message = {
|
||||
statusWaiting: '进行中...',
|
||||
records: '任务输出',
|
||||
group: '分组',
|
||||
default: '默认',
|
||||
createdAt: '创建时间',
|
||||
publishedAt: '发布时间',
|
||||
date: '时间',
|
||||
@ -1796,7 +1797,6 @@ const message = {
|
||||
nginxPer: '性能调整',
|
||||
neverExpire: '永不过期',
|
||||
setDefault: '设为默认',
|
||||
default: '默认',
|
||||
deleteHelper: '相关应用状态不正常,请检查',
|
||||
toApp: '去已安装列表',
|
||||
cycle: '周期',
|
||||
@ -2170,7 +2170,6 @@ const message = {
|
||||
ustc: '中国科学技术大学',
|
||||
netease: '网易',
|
||||
aliyun: '阿里云',
|
||||
default: '默认',
|
||||
tsinghua: '清华大学',
|
||||
xtomhk: 'XTOM 镜像站(香港)',
|
||||
xtom: 'XTOM 镜像站(全球)',
|
||||
|
@ -35,7 +35,7 @@
|
||||
<el-table-column :label="$t('commons.table.port')" prop="port" />
|
||||
<el-table-column :label="$t('commons.table.group')" show-overflow-tooltip prop="groupBelong">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.groupBelong === 'default'">{{ $t('website.default') }}</span>
|
||||
<span v-if="row.groupBelong === 'default'">{{ $t('commons.table.default') }}</span>
|
||||
<span v-else>{{ row.groupBelong }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -100,7 +100,7 @@
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span class="custom-tree-node">
|
||||
<span v-if="node.label === 'default'">{{ $t('website.default') }}</span>
|
||||
<span v-if="node.label === 'default'">{{ $t('commons.table.default') }}</span>
|
||||
<div v-else>
|
||||
<span v-if="node.label.length <= 25">
|
||||
<a @click="onClickConn(node, data)">{{ node.label }}</a>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<el-form-item :label="$t('toolbox.device.syncSite')" prop="ntpSite" :rules="Rules.domain">
|
||||
<el-input v-model="form.ntpSite" />
|
||||
<el-button type="primary" link class="tagClass" @click="form.ntpSite = 'pool.ntp.org'">
|
||||
{{ $t('website.default') }}
|
||||
{{ $t('commons.table.default') }}
|
||||
</el-button>
|
||||
<el-button type="primary" link class="tagClass" @click="form.ntpSite = 'ntp.aliyun.com'">
|
||||
{{ $t('toolbox.device.ntpALi') }}
|
||||
|
@ -265,7 +265,7 @@ const hasPnpm = computed(() => {
|
||||
|
||||
const imageSources = [
|
||||
{
|
||||
label: i18n.global.t('runtime.default'),
|
||||
label: i18n.global.t('commons.table.default'),
|
||||
value: 'https://registry.npmjs.org/',
|
||||
},
|
||||
{
|
||||
|
@ -243,7 +243,7 @@ const phpSources = [
|
||||
value: 'mirrors.xtom.com',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('runtime.default'),
|
||||
label: i18n.global.t('commons.table.default'),
|
||||
value: 'dl-cdn.alpinelinux.org',
|
||||
},
|
||||
];
|
||||
|
@ -51,7 +51,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||
open: viteEnv.VITE_OPEN,
|
||||
host: '0.0.0.0',
|
||||
proxy: {
|
||||
'/api/v1': {
|
||||
'/api/v2': {
|
||||
target: 'http://localhost:9999/',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
|
Loading…
Reference in New Issue
Block a user