更换日志组件为logrus,修改资产的账号和密码不再为必填选项
This commit is contained in:
parent
20030dac7d
commit
bbeed5344a
4
go.mod
4
go.mod
@ -3,16 +3,18 @@ module next-terminal
|
|||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/antonfisher/nested-logrus-formatter v1.3.0
|
||||||
github.com/gofrs/uuid v3.3.0+incompatible
|
github.com/gofrs/uuid v3.3.0+incompatible
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/labstack/echo/v4 v4.1.17
|
github.com/labstack/echo/v4 v4.1.17
|
||||||
github.com/labstack/gommon v0.3.0
|
github.com/labstack/gommon v0.3.0
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkg/sftp v1.12.0
|
github.com/pkg/sftp v1.12.0
|
||||||
|
github.com/sirupsen/logrus v1.2.0
|
||||||
github.com/spf13/pflag v1.0.3
|
github.com/spf13/pflag v1.0.3
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/spf13/viper v1.7.1
|
||||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
||||||
gorm.io/driver/mysql v1.0.3
|
gorm.io/driver/mysql v1.0.3
|
||||||
gorm.io/driver/sqlite v1.1.4 // indirect
|
gorm.io/driver/sqlite v1.1.4
|
||||||
gorm.io/gorm v1.20.7
|
gorm.io/gorm v1.20.7
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@ -17,6 +17,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
|||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/antonfisher/nested-logrus-formatter v1.3.0 h1:8zixYquU1Odk+vzAaAQPAdRh1ZjmUXNQ1T+dUBvlhVo=
|
||||||
|
github.com/antonfisher/nested-logrus-formatter v1.3.0/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
26
main.go
26
main.go
@ -1,19 +1,23 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
nested "github.com/antonfisher/nested-logrus-formatter"
|
||||||
"github.com/labstack/gommon/log"
|
"github.com/labstack/gommon/log"
|
||||||
"github.com/patrickmn/go-cache"
|
"github.com/patrickmn/go-cache"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"io"
|
||||||
"next-terminal/pkg/api"
|
"next-terminal/pkg/api"
|
||||||
"next-terminal/pkg/config"
|
"next-terminal/pkg/config"
|
||||||
"next-terminal/pkg/global"
|
"next-terminal/pkg/global"
|
||||||
"next-terminal/pkg/handle"
|
"next-terminal/pkg/handle"
|
||||||
"next-terminal/pkg/model"
|
"next-terminal/pkg/model"
|
||||||
"next-terminal/pkg/utils"
|
"next-terminal/pkg/utils"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -23,12 +27,29 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Run() error {
|
func Run() error {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
logrus.SetReportCaller(true)
|
||||||
|
logrus.SetFormatter(&nested.Formatter{
|
||||||
|
HideKeys: true,
|
||||||
|
FieldsOrder: []string{"component", "category"},
|
||||||
|
})
|
||||||
|
|
||||||
|
writer1 := &bytes.Buffer{}
|
||||||
|
writer2 := os.Stdout
|
||||||
|
writer3, err := os.OpenFile("next-terminal.log", os.O_WRONLY|os.O_CREATE, 0755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("create file log.txt failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.SetOutput(io.MultiWriter(writer1, writer2, writer3))
|
||||||
|
|
||||||
global.Config, err = config.SetupConfig()
|
global.Config, err = config.SetupConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.Infof("当前数据库模式为:%v", global.Config.DB)
|
||||||
if global.Config.DB == "mysql" {
|
if global.Config.DB == "mysql" {
|
||||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||||
global.Config.Mysql.Username,
|
global.Config.Mysql.Username,
|
||||||
@ -38,13 +59,14 @@ func Run() error {
|
|||||||
global.Config.Mysql.Database,
|
global.Config.Mysql.Database,
|
||||||
)
|
)
|
||||||
global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
|
global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||||
Logger: logger.Default.LogMode(logger.Info),
|
//Logger: logger.Default.LogMode(logger.Info),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
global.DB, err = gorm.Open(sqlite.Open(global.Config.Sqlite.File), &gorm.Config{})
|
global.DB, err = gorm.Open(sqlite.Open(global.Config.Sqlite.File), &gorm.Config{})
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logrus.Errorf("连接数据库异常:%v", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"next-terminal/pkg/global"
|
"next-terminal/pkg/global"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -35,7 +36,7 @@ func Auth(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
token := GetToken(c)
|
token := GetToken(c)
|
||||||
user, found := global.Cache.Get(token)
|
user, found := global.Cache.Get(token)
|
||||||
if !found {
|
if !found {
|
||||||
c.Logger().Error("您的登录信息已失效,请重新登录后再试。")
|
logrus.Debugf("您的登录信息已失效,请重新登录后再试。")
|
||||||
return Fail(c, 403, "您的登录信息已失效,请重新登录后再试。")
|
return Fail(c, 403, "您的登录信息已失效,请重新登录后再试。")
|
||||||
}
|
}
|
||||||
global.Cache.Set(token, user, time.Minute*time.Duration(30))
|
global.Cache.Set(token, user, time.Minute*time.Duration(30))
|
||||||
|
@ -19,9 +19,6 @@ func SetupRoutes() *echo.Echo {
|
|||||||
e.File("/favicon.ico", "web/build/favicon.ico")
|
e.File("/favicon.ico", "web/build/favicon.ico")
|
||||||
e.Static("/static", "web/build/static")
|
e.Static("/static", "web/build/static")
|
||||||
|
|
||||||
// Middleware
|
|
||||||
e.Use(middleware.Logger())
|
|
||||||
|
|
||||||
//fd, _ := os.OpenFile(
|
//fd, _ := os.OpenFile(
|
||||||
// "next-terminal.log",
|
// "next-terminal.log",
|
||||||
// os.O_RDWR|os.O_APPEND,
|
// os.O_RDWR|os.O_APPEND,
|
||||||
|
@ -4,8 +4,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/gommon/log"
|
"github.com/sirupsen/logrus"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -16,6 +17,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SessionPagingEndpoint(c echo.Context) error {
|
func SessionPagingEndpoint(c echo.Context) error {
|
||||||
@ -38,10 +40,10 @@ func SessionPagingEndpoint(c echo.Context) error {
|
|||||||
recording := items[i].Recording + "/recording"
|
recording := items[i].Recording + "/recording"
|
||||||
|
|
||||||
if utils.FileExists(recording) {
|
if utils.FileExists(recording) {
|
||||||
log.Infof("检测到录屏文件[%v]存在", recording)
|
logrus.Debugf("检测到录屏文件[%v]存在", recording)
|
||||||
items[i].Recording = "1"
|
items[i].Recording = "1"
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("检测到录屏文件[%v]不存在", recording)
|
logrus.Warnf("检测到录屏文件[%v]不存在", recording)
|
||||||
items[i].Recording = "0"
|
items[i].Recording = "0"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -89,16 +91,18 @@ func SessionDiscontentEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
split := strings.Split(sessionIds, ",")
|
split := strings.Split(sessionIds, ",")
|
||||||
for i := range split {
|
for i := range split {
|
||||||
tun, ok := global.Store.Get(split[i])
|
CloseSessionById(split[i], 2001, "管理员强制关闭了此次接入。")
|
||||||
if ok {
|
|
||||||
CloseSession(split[i], tun)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Success(c, nil)
|
return Success(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CloseSession(sessionId string, tun global.Tun) {
|
func CloseSessionById(sessionId string, code int, reason string) {
|
||||||
_ = tun.Tun.Close()
|
tun, _ := global.Store.Get(sessionId)
|
||||||
|
if tun != nil {
|
||||||
|
_ = tun.Tun.Close()
|
||||||
|
CloseSessionByWebSocket(tun.WebSocket, code, reason)
|
||||||
|
}
|
||||||
|
|
||||||
global.Store.Del(sessionId)
|
global.Store.Del(sessionId)
|
||||||
|
|
||||||
session := model.Session{}
|
session := model.Session{}
|
||||||
@ -109,6 +113,21 @@ func CloseSession(sessionId string, tun global.Tun) {
|
|||||||
model.UpdateSessionById(&session, sessionId)
|
model.UpdateSessionById(&session, sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CloseSessionByWebSocket(ws *websocket.Conn, c int, t string) {
|
||||||
|
if ws == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ws.SetCloseHandler(func(code int, text string) error {
|
||||||
|
var message []byte
|
||||||
|
if code != websocket.CloseNoStatusReceived {
|
||||||
|
message = websocket.FormatCloseMessage(c, t)
|
||||||
|
}
|
||||||
|
_ = ws.WriteControl(websocket.CloseMessage, message, time.Now().Add(time.Second))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
defer ws.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func SessionResizeEndpoint(c echo.Context) error {
|
func SessionResizeEndpoint(c echo.Context) error {
|
||||||
width := c.QueryParam("width")
|
width := c.QueryParam("width")
|
||||||
height := c.QueryParam("height")
|
height := c.QueryParam("height")
|
||||||
@ -457,6 +476,6 @@ func SessionRecordingEndpoint(c echo.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
recording := path.Join(session.Recording, "recording")
|
recording := path.Join(session.Recording, "recording")
|
||||||
log.Printf("读取录屏文件:%s", recording)
|
logrus.Debugf("读取录屏文件:%s", recording)
|
||||||
return c.File(recording)
|
return c.File(recording)
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"next-terminal/pkg/model"
|
"next-terminal/pkg/model"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -111,7 +111,7 @@ func SSHEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
_, err = stdinPipe.Write(message)
|
_, err = stdinPipe.Write(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Tunnel write:", err)
|
logrus.Debugf("Tunnel write: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -173,7 +173,7 @@ func WriteMessage(ws *websocket.Conn, message string) {
|
|||||||
func WriteByteMessage(ws *websocket.Conn, p []byte) {
|
func WriteByteMessage(ws *websocket.Conn, p []byte) {
|
||||||
err := ws.WriteMessage(websocket.TextMessage, p)
|
err := ws.WriteMessage(websocket.TextMessage, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("write:", err)
|
logrus.Debugf("write: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/pkg/sftp"
|
"github.com/sirupsen/logrus"
|
||||||
"log"
|
|
||||||
"next-terminal/pkg/global"
|
"next-terminal/pkg/global"
|
||||||
"next-terminal/pkg/guacd"
|
"next-terminal/pkg/guacd"
|
||||||
"next-terminal/pkg/model"
|
"next-terminal/pkg/model"
|
||||||
@ -17,6 +15,7 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logrus.Errorf("升级为WebSocket协议失败:%v", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +34,6 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
propertyMap := model.FindAllPropertiesMap()
|
propertyMap := model.FindAllPropertiesMap()
|
||||||
|
|
||||||
var session model.Session
|
var session model.Session
|
||||||
var sftpClient *sftp.Client
|
|
||||||
|
|
||||||
if len(connectionId) > 0 {
|
if len(connectionId) > 0 {
|
||||||
session, err = model.FindSessionByConnectionId(connectionId)
|
session, err = model.FindSessionByConnectionId(connectionId)
|
||||||
@ -95,11 +93,6 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
configuration.SetParameter(guacd.FontSize, strconv.Itoa(fontSize))
|
configuration.SetParameter(guacd.FontSize, strconv.Itoa(fontSize))
|
||||||
configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName])
|
configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName])
|
||||||
configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme])
|
configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme])
|
||||||
|
|
||||||
sftpClient, err = CreateSftpClient(session.AssetId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
case "vnc":
|
case "vnc":
|
||||||
configuration.SetParameter("password", session.Password)
|
configuration.SetParameter("password", session.Password)
|
||||||
@ -117,21 +110,20 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addr := propertyMap[guacd.Host] + ":" + propertyMap[guacd.Port]
|
addr := propertyMap[guacd.Host] + ":" + propertyMap[guacd.Port]
|
||||||
|
|
||||||
|
logrus.Infof("connect to %v with global: %+v", addr, configuration)
|
||||||
|
|
||||||
tunnel, err := guacd.NewTunnel(addr, configuration)
|
tunnel, err := guacd.NewTunnel(addr, configuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("=====================================================\n")
|
|
||||||
fmt.Printf("connect to %v with global: %+v\n", addr, configuration)
|
|
||||||
fmt.Printf("=====================================================\n")
|
|
||||||
|
|
||||||
tun := global.Tun{
|
tun := global.Tun{
|
||||||
Tun: tunnel,
|
Tun: tunnel,
|
||||||
SftpClient: sftpClient,
|
WebSocket: ws,
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Store.Set(sessionId, tun)
|
global.Store.Set(sessionId, &tun)
|
||||||
|
|
||||||
if len(session.ConnectionId) == 0 {
|
if len(session.ConnectionId) == 0 {
|
||||||
session.ConnectionId = tunnel.UUID
|
session.ConnectionId = tunnel.UUID
|
||||||
@ -142,19 +134,30 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
model.UpdateSessionById(&session, sessionId)
|
model.UpdateSessionById(&session, sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
sftpClient, err := CreateSftpClient(session.AssetId)
|
||||||
|
if err != nil {
|
||||||
|
CloseSessionById(sessionId, 2002, err.Error())
|
||||||
|
logrus.Errorf("创建sftp客户端失败:%v", err.Error())
|
||||||
|
}
|
||||||
|
item, ok := global.Store.Get(sessionId)
|
||||||
|
if ok {
|
||||||
|
item.SftpClient = sftpClient
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for true {
|
for true {
|
||||||
instruction, err := tunnel.Read()
|
instruction, err := tunnel.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
CloseSession(sessionId, tun)
|
CloseSessionById(sessionId, 523, err.Error())
|
||||||
log.Printf("WS读取异常: %v", err)
|
logrus.Printf("WebSocket读取错误: %v", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
//fmt.Printf("<= %v \n", string(instruction))
|
|
||||||
err = ws.WriteMessage(websocket.TextMessage, instruction)
|
err = ws.WriteMessage(websocket.TextMessage, instruction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
CloseSession(sessionId, tun)
|
CloseSessionById(sessionId, 523, err.Error())
|
||||||
log.Printf("WS写入异常: %v", err)
|
logrus.Printf("WebSocket写入错误: %v", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,14 +166,14 @@ func TunEndpoint(c echo.Context) error {
|
|||||||
for true {
|
for true {
|
||||||
_, message, err := ws.ReadMessage()
|
_, message, err := ws.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
CloseSession(sessionId, tun)
|
CloseSessionById(sessionId, 523, err.Error())
|
||||||
log.Printf("Tunnel读取异常: %v", err)
|
logrus.Printf("隧道读取错误: %v", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
_, err = tunnel.WriteAndFlush(message)
|
_, err = tunnel.WriteAndFlush(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
CloseSession(sessionId, tun)
|
CloseSessionById(sessionId, 523, err.Error())
|
||||||
log.Printf("Tunnel写入异常: %v", err)
|
logrus.Printf("隧道写入错误: %v", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/spf13/pflag"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -39,10 +40,19 @@ func SetupConfig() (*Config, error) {
|
|||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
|
||||||
err := viper.ReadInConfig()
|
pflag.String("db", "sqlite", "db mode")
|
||||||
if err != nil {
|
pflag.String("sqlite.file", "next-terminal.db", "sqlite db file")
|
||||||
return nil, err
|
pflag.String("mysql.hostname", "127.0.0.1", "mysql hostname")
|
||||||
}
|
pflag.Int("mysql.port", 3306, "mysql port")
|
||||||
|
pflag.String("mysql.username", "mysql", "mysql username")
|
||||||
|
pflag.String("mysql.password", "mysql", "mysql password")
|
||||||
|
pflag.String("mysql.database", "next_terminal", "mysql database")
|
||||||
|
|
||||||
|
pflag.String("server.addr", "0.0.0.0:8088", "server listen addr")
|
||||||
|
|
||||||
|
pflag.Parse()
|
||||||
|
_ = viper.BindPFlags(pflag.CommandLine)
|
||||||
|
_ = viper.ReadInConfig()
|
||||||
|
|
||||||
var config = &Config{
|
var config = &Config{
|
||||||
DB: viper.GetString("db"),
|
DB: viper.GetString("db"),
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
package global
|
package global
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
"next-terminal/pkg/guacd"
|
"next-terminal/pkg/guacd"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tun struct {
|
type Tun struct {
|
||||||
Tun guacd.Tunnel
|
Tun *guacd.Tunnel
|
||||||
SftpClient *sftp.Client
|
SftpClient *sftp.Client
|
||||||
|
WebSocket *websocket.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunStore struct {
|
type TunStore struct {
|
||||||
m sync.Map
|
m sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TunStore) Set(k string, v Tun) {
|
func (s *TunStore) Set(k string, v *Tun) {
|
||||||
s.m.Store(k, v)
|
s.m.Store(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,10 +25,10 @@ func (s *TunStore) Del(k string) {
|
|||||||
s.m.Delete(k)
|
s.m.Delete(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TunStore) Get(k string) (item Tun, ok bool) {
|
func (s *TunStore) Get(k string) (item *Tun, ok bool) {
|
||||||
value, ok := s.m.Load(k)
|
value, ok := s.m.Load(k)
|
||||||
if ok {
|
if ok {
|
||||||
return value.(Tun), true
|
return value.(*Tun), true
|
||||||
}
|
}
|
||||||
return item, false
|
return item, false
|
||||||
}
|
}
|
||||||
|
@ -106,13 +106,14 @@ type Tunnel struct {
|
|||||||
IsOpen bool
|
IsOpen bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTunnel(address string, config Configuration) (ret Tunnel, err error) {
|
func NewTunnel(address string, config Configuration) (ret *Tunnel, err error) {
|
||||||
|
|
||||||
conn, err := net.Dial("tcp", address)
|
conn, err := net.Dial("tcp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = &Tunnel{}
|
||||||
ret.conn = conn
|
ret.conn = conn
|
||||||
ret.rw = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
ret.rw = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
||||||
ret.Config = config
|
ret.Config = config
|
||||||
@ -123,7 +124,7 @@ func NewTunnel(address string, config Configuration) (ret Tunnel, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := ret.WriteInstructionAndFlush(NewInstruction("select", selectArg)); err != nil {
|
if err := ret.WriteInstructionAndFlush(NewInstruction("select", selectArg)); err != nil {
|
||||||
return Tunnel{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
args, err := ret.expect("args")
|
args, err := ret.expect("args")
|
||||||
@ -135,21 +136,21 @@ func NewTunnel(address string, config Configuration) (ret Tunnel, err error) {
|
|||||||
height := config.GetParameter("height")
|
height := config.GetParameter("height")
|
||||||
// send size
|
// send size
|
||||||
if err := ret.WriteInstructionAndFlush(NewInstruction("size", width, height, "96")); err != nil {
|
if err := ret.WriteInstructionAndFlush(NewInstruction("size", width, height, "96")); err != nil {
|
||||||
return Tunnel{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ret.WriteInstructionAndFlush(NewInstruction("audio")); err != nil {
|
if err := ret.WriteInstructionAndFlush(NewInstruction("audio")); err != nil {
|
||||||
return Tunnel{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := ret.WriteInstructionAndFlush(NewInstruction("video")); err != nil {
|
if err := ret.WriteInstructionAndFlush(NewInstruction("video")); err != nil {
|
||||||
return Tunnel{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := ret.WriteInstructionAndFlush(NewInstruction("image")); err != nil {
|
if err := ret.WriteInstructionAndFlush(NewInstruction("image")); err != nil {
|
||||||
return Tunnel{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ret.WriteInstructionAndFlush(NewInstruction("timezone", "Asia/Shanghai")); err != nil {
|
if err := ret.WriteInstructionAndFlush(NewInstruction("timezone", "Asia/Shanghai")); err != nil {
|
||||||
return Tunnel{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
parameters := make([]string, len(args.Args))
|
parameters := make([]string, len(args.Args))
|
||||||
@ -163,7 +164,7 @@ func NewTunnel(address string, config Configuration) (ret Tunnel, err error) {
|
|||||||
}
|
}
|
||||||
// send connect
|
// send connect
|
||||||
if err := ret.WriteInstructionAndFlush(NewInstruction("connect", parameters...)); err != nil {
|
if err := ret.WriteInstructionAndFlush(NewInstruction("connect", parameters...)); err != nil {
|
||||||
return Tunnel{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ready, err := ret.expect("ready")
|
ready, err := ret.expect("ready")
|
||||||
@ -172,7 +173,7 @@ func NewTunnel(address string, config Configuration) (ret Tunnel, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(ready.Args) == 0 {
|
if len(ready.Args) == 0 {
|
||||||
return ret, errors.New("no connection id received")
|
return nil, errors.New("no connection id received")
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.UUID = ready.Args[0]
|
ret.UUID = ready.Args[0]
|
||||||
@ -225,7 +226,6 @@ func (opt *Tunnel) ReadInstruction() (instruction Instruction, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return instruction, err
|
return instruction, err
|
||||||
}
|
}
|
||||||
fmt.Printf("<- %v \n", msg)
|
|
||||||
return instruction.Parse(msg), err
|
return instruction.Parse(msg), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,7 +386,7 @@ class Asset extends Component {
|
|||||||
itemRender: itemRender
|
itemRender: itemRender
|
||||||
}}
|
}}
|
||||||
extra={[
|
extra={[
|
||||||
<Logout/>
|
<Logout key='logout'/>
|
||||||
]}
|
]}
|
||||||
subTitle="资产"
|
subTitle="资产"
|
||||||
>
|
>
|
||||||
|
@ -7,12 +7,29 @@ const {Option} = Select;
|
|||||||
// 子级页面
|
// 子级页面
|
||||||
// Ant form create 表单内置方法
|
// Ant form create 表单内置方法
|
||||||
|
|
||||||
|
const protocolMapping = {
|
||||||
|
'ssh': [
|
||||||
|
{text: '自定义', value: 'custom'},
|
||||||
|
{text: '授权凭证', value: 'credential'},
|
||||||
|
{text: '私钥', value: 'private-key'}
|
||||||
|
],
|
||||||
|
'rdp': [{text: '自定义', value: 'custom'}, {text: '授权凭证', value: 'credential'}],
|
||||||
|
'vnc': [{text: '自定义', value: 'custom'}, {text: '授权凭证', value: 'credential'}],
|
||||||
|
'telnet': [{text: '自定义', value: 'custom'}, {text: '授权凭证', value: 'credential'}]
|
||||||
|
}
|
||||||
|
|
||||||
const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoading, credentials, model}) {
|
const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoading, credentials, model}) {
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
let [accountType, setAccountType] = useState(model.accountType);
|
let [accountType, setAccountType] = useState(model.accountType);
|
||||||
|
|
||||||
|
let initAccountTypes = []
|
||||||
|
if (model.protocol) {
|
||||||
|
initAccountTypes = protocolMapping[model.protocol];
|
||||||
|
}
|
||||||
|
let [accountTypes, setAccountTypes] = useState(initAccountTypes);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAccountType(model.accountType);
|
setAccountType(model.accountType);
|
||||||
});
|
});
|
||||||
@ -35,18 +52,35 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa
|
|||||||
switch (e.target.value) {
|
switch (e.target.value) {
|
||||||
case 'ssh':
|
case 'ssh':
|
||||||
port = 22;
|
port = 22;
|
||||||
break;
|
setAccountTypes(protocolMapping['ssh']);
|
||||||
case 'rdp':
|
|
||||||
port = 3389;
|
|
||||||
break;
|
|
||||||
case 'vnc':
|
|
||||||
port = 5901;
|
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
accountType: 'custom',
|
accountType: 'custom',
|
||||||
});
|
});
|
||||||
|
handleAccountTypeChange('custom');
|
||||||
|
break;
|
||||||
|
case 'rdp':
|
||||||
|
port = 3389;
|
||||||
|
setAccountTypes(protocolMapping['rdp']);
|
||||||
|
form.setFieldsValue({
|
||||||
|
accountType: 'custom',
|
||||||
|
});
|
||||||
|
handleAccountTypeChange('custom');
|
||||||
|
break;
|
||||||
|
case 'vnc':
|
||||||
|
port = 5901;
|
||||||
|
setAccountTypes(protocolMapping['vnc']);
|
||||||
|
form.setFieldsValue({
|
||||||
|
accountType: 'custom',
|
||||||
|
});
|
||||||
|
handleAccountTypeChange('custom');
|
||||||
break;
|
break;
|
||||||
case 'telnet':
|
case 'telnet':
|
||||||
port = 23;
|
port = 23;
|
||||||
|
setAccountTypes(protocolMapping['telnet']);
|
||||||
|
form.setFieldsValue({
|
||||||
|
accountType: 'custom',
|
||||||
|
});
|
||||||
|
handleAccountTypeChange('custom');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
port = 65535;
|
port = 65535;
|
||||||
@ -57,6 +91,11 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAccountTypeChange = v => {
|
||||||
|
setAccountType(v);
|
||||||
|
model.accountType = v;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
@ -106,13 +145,10 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label="账户类型" name='accountType' rules={[{required: true, message: '请选择接账户类型'}]}>
|
<Form.Item label="账户类型" name='accountType' rules={[{required: true, message: '请选择接账户类型'}]}>
|
||||||
<Select onChange={(v) => {
|
<Select onChange={handleAccountTypeChange}>
|
||||||
setAccountType(v);
|
{accountTypes.map(item => {
|
||||||
model.accountType = v;
|
return (<Option key={item.value} value={item.value}>{item.text}</Option>)
|
||||||
}}>
|
})}
|
||||||
<Option value="custom">自定义</Option>
|
|
||||||
<Option value="credential">授权凭证</Option>
|
|
||||||
<Option value="private-key">私钥</Option>
|
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -138,13 +174,11 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa
|
|||||||
{
|
{
|
||||||
accountType === 'custom' ?
|
accountType === 'custom' ?
|
||||||
<>
|
<>
|
||||||
<Form.Item label="授权账户" name='username' rules={[{required: true, message: '请输入授权账户'}]}
|
<Form.Item label="授权账户" name='username' noStyle={!(accountType === 'custom')}>
|
||||||
noStyle={!(accountType === 'custom')}>
|
|
||||||
<Input placeholder="输入授权账户"/>
|
<Input placeholder="输入授权账户"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label="授权密码" name='password' rules={[{required: true, message: '请输入授权密码'}]}
|
<Form.Item label="授权密码" name='password' noStyle={!(accountType === 'custom')}>
|
||||||
noStyle={!(accountType === 'custom')}>
|
|
||||||
<Input placeholder="输入授权密码"/>
|
<Input placeholder="输入授权密码"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
|
@ -57,7 +57,7 @@ class BatchCommand extends Component {
|
|||||||
itemRender: itemRender
|
itemRender: itemRender
|
||||||
}}
|
}}
|
||||||
extra={[
|
extra={[
|
||||||
<Logout/>
|
<Logout key='logout'/>
|
||||||
]}
|
]}
|
||||||
subTitle="动态指令"
|
subTitle="动态指令"
|
||||||
>
|
>
|
||||||
|
@ -361,7 +361,7 @@ class DynamicCommand extends Component {
|
|||||||
itemRender: itemRender
|
itemRender: itemRender
|
||||||
}}
|
}}
|
||||||
extra={[
|
extra={[
|
||||||
<Logout/>
|
<Logout key='logout'/>
|
||||||
]}
|
]}
|
||||||
subTitle="批量动态指令执行"
|
subTitle="批量动态指令执行"
|
||||||
>
|
>
|
||||||
|
@ -282,7 +282,7 @@ class Credential extends Component {
|
|||||||
itemRender: itemRender
|
itemRender: itemRender
|
||||||
}}
|
}}
|
||||||
extra={[
|
extra={[
|
||||||
<Logout/>
|
<Logout key='logout'/>
|
||||||
]}
|
]}
|
||||||
subTitle="访问资产的账户、密钥等"
|
subTitle="访问资产的账户、密钥等"
|
||||||
>
|
>
|
||||||
|
@ -88,7 +88,7 @@ class Dashboard extends Component {
|
|||||||
}}
|
}}
|
||||||
subTitle="仪表盘"
|
subTitle="仪表盘"
|
||||||
extra={[
|
extra={[
|
||||||
<Logout/>
|
<Logout key='logout'/>
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
@ -359,7 +359,7 @@ class OfflineSession extends Component {
|
|||||||
itemRender: itemRender
|
itemRender: itemRender
|
||||||
}}
|
}}
|
||||||
extra={[
|
extra={[
|
||||||
<Logout/>
|
<Logout key='logout'/>
|
||||||
]}
|
]}
|
||||||
subTitle="离线会话管理"
|
subTitle="离线会话管理"
|
||||||
>
|
>
|
||||||
|
@ -350,7 +350,7 @@ class OnlineSession extends Component {
|
|||||||
itemRender: itemRender
|
itemRender: itemRender
|
||||||
}}
|
}}
|
||||||
extra={[
|
extra={[
|
||||||
<Logout/>
|
<Logout key='logout'/>
|
||||||
]}
|
]}
|
||||||
subTitle="查询实时在线会话"
|
subTitle="查询实时在线会话"
|
||||||
>
|
>
|
||||||
|
@ -109,7 +109,7 @@ class Setting extends Component {
|
|||||||
itemRender: itemRender
|
itemRender: itemRender
|
||||||
}}
|
}}
|
||||||
extra={[
|
extra={[
|
||||||
<Logout/>
|
<Logout key='logout'/>
|
||||||
]}
|
]}
|
||||||
subTitle="系统设置"
|
subTitle="系统设置"
|
||||||
>
|
>
|
||||||
|
@ -80,7 +80,7 @@ class Info extends Component {
|
|||||||
itemRender: itemRender
|
itemRender: itemRender
|
||||||
}}
|
}}
|
||||||
extra={[
|
extra={[
|
||||||
<Logout/>
|
<Logout key='logout'/>
|
||||||
]}
|
]}
|
||||||
subTitle="个人中心"
|
subTitle="个人中心"
|
||||||
>
|
>
|
||||||
|
@ -18,6 +18,7 @@ class Logout extends Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
|
key='login-btn-pop'
|
||||||
title="您确定要退出登录吗?"
|
title="您确定要退出登录吗?"
|
||||||
onConfirm={this.confirm}
|
onConfirm={this.confirm}
|
||||||
okText="确定"
|
okText="确定"
|
||||||
|
@ -310,7 +310,7 @@ class User extends Component {
|
|||||||
itemRender: itemRender
|
itemRender: itemRender
|
||||||
}}
|
}}
|
||||||
extra={[
|
extra={[
|
||||||
<Logout/>
|
<Logout key='logout'/>
|
||||||
]}
|
]}
|
||||||
subTitle="平台用户管理"
|
subTitle="平台用户管理"
|
||||||
>
|
>
|
||||||
|
Loading…
Reference in New Issue
Block a user