Initial commit
This commit is contained in:
52
pkg/api/account.go
Normal file
52
pkg/api/account.go
Normal file
@ -0,0 +1,52 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"next-terminal/pkg/config"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LoginAccount struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func LoginEndpoint(c echo.Context) error {
|
||||
var loginAccount LoginAccount
|
||||
if err := c.Bind(&loginAccount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := model.FindUserByUsername(loginAccount.Username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token := utils.UUID()
|
||||
|
||||
config.Cache.Set(token, user, time.Minute*time.Duration(30))
|
||||
|
||||
model.UpdateUserById(&model.User{Online: true}, user.ID)
|
||||
|
||||
return Success(c, token)
|
||||
}
|
||||
|
||||
func LogoutEndpoint(c echo.Context) error {
|
||||
token := GetToken(c)
|
||||
config.Cache.Delete(token)
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func ChangePasswordEndpoint(c echo.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func InfoEndpoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
return Success(c, account)
|
||||
}
|
96
pkg/api/asset.go
Normal file
96
pkg/api/asset.go
Normal file
@ -0,0 +1,96 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"github.com/labstack/echo/v4"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func AssetCreateEndpoint(c echo.Context) error {
|
||||
var item model.Asset
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item.ID = utils.UUID()
|
||||
item.Created = utils.NowJsonTime()
|
||||
|
||||
if err := model.CreateNewAsset(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func AssetPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
name := c.QueryParam("name")
|
||||
protocol := c.QueryParam("protocol")
|
||||
|
||||
items, total, _ := model.FindPageAsset(pageIndex, pageSize, name, protocol)
|
||||
|
||||
return Success(c, H{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func AssetAllEndpoint(c echo.Context) error {
|
||||
protocol := c.QueryParam("protocol")
|
||||
items, _ := model.FindAssetByConditions(protocol)
|
||||
return Success(c, items)
|
||||
}
|
||||
|
||||
func AssetUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
var item model.Asset
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model.UpdateAssetById(&item, id)
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func AssetDeleteEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
split := strings.Split(id, ",")
|
||||
for i := range split {
|
||||
model.DeleteAssetById(split[i])
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func AssetGetEndpoint(c echo.Context) (err error) {
|
||||
id := c.Param("id")
|
||||
|
||||
var item model.Asset
|
||||
if item, err = model.FindAssetById(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func AssetTcpingEndpoint(c echo.Context) (err error) {
|
||||
id := c.Param("id")
|
||||
|
||||
var item model.Asset
|
||||
if item, err = model.FindAssetById(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
active := utils.Tcping(item.IP, item.Port)
|
||||
asset := model.Asset{
|
||||
Active: active,
|
||||
}
|
||||
|
||||
model.UpdateAssetById(&asset, item.ID)
|
||||
return Success(c, active)
|
||||
}
|
70
pkg/api/command.go
Normal file
70
pkg/api/command.go
Normal file
@ -0,0 +1,70 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CommandCreateEndpoint(c echo.Context) error {
|
||||
var item model.Command
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item.ID = utils.UUID()
|
||||
item.Created = utils.NowJsonTime()
|
||||
|
||||
if err := model.CreateNewCommand(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func CommandPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
name := c.QueryParam("name")
|
||||
content := c.QueryParam("content")
|
||||
|
||||
items, total, _ := model.FindPageCommand(pageIndex, pageSize, name, content)
|
||||
|
||||
return Success(c, H{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func CommandUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
var item model.Command
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model.UpdateCommandById(&item, id)
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func CommandDeleteEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
split := strings.Split(id, ",")
|
||||
for i := range split {
|
||||
model.DeleteCommandById(split[i])
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func CommandGetEndpoint(c echo.Context) (err error) {
|
||||
id := c.Param("id")
|
||||
var item model.Command
|
||||
if item, err = model.FindCommandById(id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, item)
|
||||
}
|
72
pkg/api/credential.go
Normal file
72
pkg/api/credential.go
Normal file
@ -0,0 +1,72 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CredentialAllEndpoint(c echo.Context) error {
|
||||
items, _ := model.FindAllCredential()
|
||||
return Success(c, items)
|
||||
}
|
||||
func CredentialCreateEndpoint(c echo.Context) error {
|
||||
var item model.Credential
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item.ID = utils.UUID()
|
||||
item.Created = utils.NowJsonTime()
|
||||
|
||||
if err := model.CreateNewCredential(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func CredentialPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
name := c.QueryParam("name")
|
||||
|
||||
items, total, _ := model.FindPageCredential(pageIndex, pageSize, name)
|
||||
|
||||
return Success(c, H{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func CredentialUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
var item model.Credential
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model.UpdateCredentialById(&item, id)
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func CredentialDeleteEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
split := strings.Split(id, ",")
|
||||
for i := range split {
|
||||
model.DeleteCredentialById(split[i])
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func CredentialGetEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
item, _ := model.FindCredentialById(id)
|
||||
|
||||
return Success(c, item)
|
||||
}
|
34
pkg/api/middleware.go
Normal file
34
pkg/api/middleware.go
Normal file
@ -0,0 +1,34 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"next-terminal/pkg/config"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Auth(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
|
||||
urls := []string{"download", "login"}
|
||||
|
||||
return func(c echo.Context) error {
|
||||
// 路由拦截 - 登录身份、资源权限判断等
|
||||
for i := range urls {
|
||||
if c.Request().RequestURI == "/" || strings.HasPrefix(c.Request().RequestURI, "/#") {
|
||||
return next(c)
|
||||
}
|
||||
if strings.Contains(c.Request().RequestURI, urls[i]) {
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
token := GetToken(c)
|
||||
user, found := config.Cache.Get(token)
|
||||
if !found {
|
||||
c.Logger().Error("您的登录信息已失效,请重新登录后再试。")
|
||||
return Fail(c, 403, "您的登录信息已失效,请重新登录后再试。")
|
||||
}
|
||||
config.Cache.Set(token, user, time.Minute*time.Duration(30))
|
||||
return next(c)
|
||||
}
|
||||
}
|
98
pkg/api/overview.go
Normal file
98
pkg/api/overview.go
Normal file
@ -0,0 +1,98 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"next-terminal/pkg/model"
|
||||
"fmt"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/load"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
"time"
|
||||
)
|
||||
|
||||
type OverviewStatus struct {
|
||||
Load Load `json:"load"`
|
||||
Memory Memory `json:"memory"`
|
||||
CPU CPU `json:"cpu"`
|
||||
}
|
||||
|
||||
type Load struct {
|
||||
Load1 float64 `json:"load1"`
|
||||
Load5 float64 `json:"load5"`
|
||||
Load15 float64 `json:"load15"`
|
||||
}
|
||||
|
||||
type Memory struct {
|
||||
Total uint64 `json:"total"`
|
||||
Available uint64 `json:"available"`
|
||||
UsedPercent float64 `json:"usedPercent"`
|
||||
Used uint64 `json:"used"`
|
||||
}
|
||||
|
||||
type CPU struct {
|
||||
PhysicalCount int `json:"physicalCount"`
|
||||
LogicalCount int `json:"logicalCount"`
|
||||
Percent float64 `json:"percent"`
|
||||
ModelName string `json:"modelName"`
|
||||
}
|
||||
|
||||
type Counter struct {
|
||||
User int64 `json:"user"`
|
||||
Asset int64 `json:"asset"`
|
||||
Credential int64 `json:"credential"`
|
||||
OnlineSession int64 `json:"onlineSession"`
|
||||
}
|
||||
|
||||
func OverviewStatusEndPoint(c echo.Context) error {
|
||||
info, _ := load.Avg()
|
||||
memory, _ := mem.VirtualMemory()
|
||||
infoStats, _ := cpu.Info()
|
||||
physicalCount, _ := cpu.Counts(false)
|
||||
logicalCount, _ := cpu.Counts(true)
|
||||
cps, _ := cpu.Percent(time.Second, false)
|
||||
|
||||
fmt.Printf("%+v\n", info)
|
||||
fmt.Printf("%+v\n", memory)
|
||||
fmt.Printf("%+v\n", infoStats)
|
||||
fmt.Printf("%+v\n", physicalCount)
|
||||
fmt.Printf("%+v\n", logicalCount)
|
||||
fmt.Printf("%+v\n", cps)
|
||||
|
||||
overviewStatus := OverviewStatus{
|
||||
Load: Load{
|
||||
Load1: info.Load1,
|
||||
Load5: info.Load5,
|
||||
Load15: info.Load15,
|
||||
},
|
||||
Memory: Memory{
|
||||
Total: memory.Total,
|
||||
Available: memory.Available,
|
||||
UsedPercent: memory.UsedPercent,
|
||||
Used: memory.Used,
|
||||
},
|
||||
CPU: CPU{
|
||||
PhysicalCount: physicalCount,
|
||||
LogicalCount: logicalCount,
|
||||
Percent: cps[0],
|
||||
ModelName: infoStats[0].ModelName,
|
||||
},
|
||||
}
|
||||
|
||||
return Success(c, overviewStatus)
|
||||
}
|
||||
|
||||
func OverviewCounterEndPoint(c echo.Context) error {
|
||||
countUser, _ := model.CountUser()
|
||||
countOnlineSession, _ := model.CountOnlineSession()
|
||||
credential, _ := model.CountCredential()
|
||||
asset, _ := model.CountAsset()
|
||||
|
||||
counter := Counter{
|
||||
User: countUser,
|
||||
OnlineSession: countOnlineSession,
|
||||
Credential: credential,
|
||||
Asset: asset,
|
||||
}
|
||||
|
||||
return Success(c, counter)
|
||||
}
|
29
pkg/api/property.go
Normal file
29
pkg/api/property.go
Normal file
@ -0,0 +1,29 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"next-terminal/pkg/model"
|
||||
"fmt"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func PropertyGetEndpoint(c echo.Context) error {
|
||||
properties := model.FindAllProperties()
|
||||
return Success(c, properties)
|
||||
}
|
||||
|
||||
func PropertyUpdateEndpoint(c echo.Context) error {
|
||||
var item map[string]interface{}
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for key := range item {
|
||||
value := fmt.Sprintf("%v", item[key])
|
||||
property := model.Property{
|
||||
Name: key,
|
||||
Value: value,
|
||||
}
|
||||
model.UpdatePropertyByName(&property, key)
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
145
pkg/api/routes.go
Normal file
145
pkg/api/routes.go
Normal file
@ -0,0 +1,145 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"net/http"
|
||||
"next-terminal/pkg/config"
|
||||
"next-terminal/pkg/model"
|
||||
)
|
||||
|
||||
const Token = "X-Auth-Token"
|
||||
|
||||
func SetupRoutes() *echo.Echo {
|
||||
|
||||
// Echo instance
|
||||
e := echo.New()
|
||||
|
||||
e.File("/", "web/build/index.html")
|
||||
e.File("/logo.svg", "web/build/logo.svg")
|
||||
e.File("/favicon.ico", "web/build/favicon.ico")
|
||||
e.Static("/static", "web/build/static")
|
||||
|
||||
// Middleware
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
Skipper: middleware.DefaultSkipper,
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
|
||||
}))
|
||||
e.Use(Auth)
|
||||
|
||||
e.POST("/login", LoginEndpoint)
|
||||
|
||||
e.GET("/tunnel", TunEndpoint)
|
||||
e.GET("/ssh", SSHEndpoint)
|
||||
|
||||
e.POST("/logout", LogoutEndpoint)
|
||||
e.POST("/change-password", ChangePasswordEndpoint)
|
||||
e.GET("/info", InfoEndpoint)
|
||||
|
||||
users := e.Group("/users")
|
||||
{
|
||||
users.POST("", UserCreateEndpoint)
|
||||
users.GET("/paging", UserPagingEndpoint)
|
||||
users.PUT("/:id", UserUpdateEndpoint)
|
||||
users.DELETE("/:id", UserDeleteEndpoint)
|
||||
users.GET("/:id", UserGetEndpoint)
|
||||
}
|
||||
|
||||
assets := e.Group("/assets", Auth)
|
||||
{
|
||||
assets.GET("", AssetAllEndpoint)
|
||||
assets.POST("", AssetCreateEndpoint)
|
||||
assets.GET("/paging", AssetPagingEndpoint)
|
||||
assets.POST("/:id/tcping", AssetTcpingEndpoint)
|
||||
assets.PUT("/:id", AssetUpdateEndpoint)
|
||||
assets.DELETE("/:id", AssetDeleteEndpoint)
|
||||
assets.GET("/:id", AssetGetEndpoint)
|
||||
}
|
||||
|
||||
commands := e.Group("/commands")
|
||||
{
|
||||
commands.GET("/paging", CommandPagingEndpoint)
|
||||
commands.POST("", CommandCreateEndpoint)
|
||||
commands.PUT("/:id", CommandUpdateEndpoint)
|
||||
commands.DELETE("/:id", CommandDeleteEndpoint)
|
||||
commands.GET("/:id", CommandGetEndpoint)
|
||||
}
|
||||
|
||||
credentials := e.Group("/credentials")
|
||||
{
|
||||
credentials.GET("", CredentialAllEndpoint)
|
||||
credentials.GET("/paging", CredentialPagingEndpoint)
|
||||
credentials.POST("", CredentialCreateEndpoint)
|
||||
credentials.PUT("/:id", CredentialUpdateEndpoint)
|
||||
credentials.DELETE("/:id", CredentialDeleteEndpoint)
|
||||
credentials.GET("/:id", CredentialGetEndpoint)
|
||||
}
|
||||
|
||||
sessions := e.Group("/sessions")
|
||||
{
|
||||
sessions.POST("", SessionCreateEndpoint)
|
||||
sessions.GET("/paging", SessionPagingEndpoint)
|
||||
sessions.POST("/:id/content", SessionContentEndpoint)
|
||||
sessions.POST("/:id/discontent", SessionDiscontentEndpoint)
|
||||
sessions.POST("/:id/resize", SessionResizeEndpoint)
|
||||
sessions.POST("/:id/upload", SessionUploadEndpoint)
|
||||
sessions.GET("/:id/download", SessionDownloadEndpoint)
|
||||
sessions.GET("/:id/ls", SessionLsEndpoint)
|
||||
sessions.POST("/:id/mkdir", SessionMkDirEndpoint)
|
||||
sessions.DELETE("/:id/rmdir", SessionRmDirEndpoint)
|
||||
sessions.DELETE("/:id/rm", SessionRmEndpoint)
|
||||
sessions.DELETE("/:id", SessionDeleteEndpoint)
|
||||
}
|
||||
|
||||
e.GET("/properties", PropertyGetEndpoint)
|
||||
e.PUT("/properties", PropertyUpdateEndpoint)
|
||||
|
||||
e.GET("/overview/status", OverviewStatusEndPoint)
|
||||
e.GET("/overview/counter", OverviewCounterEndPoint)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
type H map[string]interface{}
|
||||
|
||||
func Fail(c echo.Context, code int, message string) error {
|
||||
return c.JSON(200, H{
|
||||
"code": code,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
func Success(c echo.Context, data interface{}) error {
|
||||
return c.JSON(200, H{
|
||||
"code": 1,
|
||||
"message": "success",
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
func NotFound(c echo.Context, message string) error {
|
||||
return c.JSON(200, H{
|
||||
"code": -1,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
func GetToken(c echo.Context) string {
|
||||
token := c.Request().Header.Get(Token)
|
||||
if len(token) > 0 {
|
||||
return token
|
||||
}
|
||||
return c.QueryParam(Token)
|
||||
}
|
||||
|
||||
func GetCurrentAccount(c echo.Context) (model.User, bool) {
|
||||
token := GetToken(c)
|
||||
get, b := config.Cache.Get(token)
|
||||
if b {
|
||||
return get.(model.User), true
|
||||
}
|
||||
return model.User{}, false
|
||||
}
|
430
pkg/api/session.go
Normal file
430
pkg/api/session.go
Normal file
@ -0,0 +1,430 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/labstack/echo/v4"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"next-terminal/pkg/config"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func SessionPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
status := c.QueryParam("status")
|
||||
userId := c.QueryParam("userId")
|
||||
clientIp := c.QueryParam("clientIp")
|
||||
assetId := c.QueryParam("assetId")
|
||||
protocol := c.QueryParam("protocol")
|
||||
|
||||
items, total, err := model.FindPageSession(pageIndex, pageSize, status, userId, clientIp, assetId, protocol)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func SessionDeleteEndpoint(c echo.Context) error {
|
||||
sessionIds := c.Param("id")
|
||||
split := strings.Split(sessionIds, ",")
|
||||
for i := range split {
|
||||
model.DeleteSessionById(split[i])
|
||||
drivePath, err := model.GetDrivePath()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
_ = os.Remove(path.Join(drivePath, split[i]))
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SessionContentEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
|
||||
session := model.Session{}
|
||||
session.ID = sessionId
|
||||
session.Status = model.Connected
|
||||
session.ConnectedTime = utils.NowJsonTime()
|
||||
|
||||
model.UpdateSessionById(&session, sessionId)
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func SessionDiscontentEndpoint(c echo.Context) error {
|
||||
sessionIds := c.Param("id")
|
||||
|
||||
split := strings.Split(sessionIds, ",")
|
||||
for i := range split {
|
||||
tun, ok := config.Store.Get(split[i])
|
||||
if ok {
|
||||
CloseSession(split[i], tun)
|
||||
}
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func CloseSession(sessionId string, tun config.Tun) {
|
||||
_ = tun.Tun.Close()
|
||||
config.Store.Del(sessionId)
|
||||
|
||||
session := model.Session{}
|
||||
session.ID = sessionId
|
||||
session.Status = model.Disconnected
|
||||
session.DisconnectedTime = utils.NowJsonTime()
|
||||
|
||||
model.UpdateSessionById(&session, sessionId)
|
||||
}
|
||||
|
||||
func SessionResizeEndpoint(c echo.Context) error {
|
||||
width := c.QueryParam("width")
|
||||
height := c.QueryParam("height")
|
||||
sessionId := c.Param("id")
|
||||
|
||||
if len(width) == 0 || len(height) == 0 {
|
||||
panic("参数异常")
|
||||
}
|
||||
|
||||
intWidth, _ := strconv.Atoi(width)
|
||||
|
||||
intHeight, _ := strconv.Atoi(height)
|
||||
|
||||
session := model.Session{}
|
||||
session.ID = sessionId
|
||||
session.Width = intWidth
|
||||
session.Height = intHeight
|
||||
|
||||
model.UpdateSessionById(&session, sessionId)
|
||||
return Success(c, session)
|
||||
}
|
||||
|
||||
func SessionCreateEndpoint(c echo.Context) error {
|
||||
assetId := c.QueryParam("assetId")
|
||||
user, _ := GetCurrentAccount(c)
|
||||
|
||||
asset, err := model.FindAssetById(assetId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
session := &model.Session{
|
||||
ID: utils.UUID(),
|
||||
AssetId: asset.ID,
|
||||
Username: asset.Username,
|
||||
Password: asset.Password,
|
||||
Protocol: asset.Protocol,
|
||||
IP: asset.IP,
|
||||
Port: asset.Port,
|
||||
Status: model.NoConnect,
|
||||
Creator: user.ID,
|
||||
ClientIP: c.RealIP(),
|
||||
}
|
||||
|
||||
if asset.AccountType == "credential" {
|
||||
credential, err := model.FindCredentialById(asset.CredentialId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
session.Username = credential.Username
|
||||
session.Password = credential.Password
|
||||
}
|
||||
|
||||
if err := model.CreateNewSession(session); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, session)
|
||||
}
|
||||
|
||||
func SessionUploadEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
session, err := model.FindSessionById(sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := file.Filename
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteDir := c.QueryParam("dir")
|
||||
remoteFile := path.Join(remoteDir, filename)
|
||||
|
||||
if "ssh" == session.Protocol {
|
||||
tun, ok := config.Store.Get(sessionId)
|
||||
if !ok {
|
||||
return errors.New("获取sftp客户端失败")
|
||||
}
|
||||
|
||||
dstFile, err := tun.SftpClient.Create(remoteFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer dstFile.Close()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, _ := src.Read(buf)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
_, _ = dstFile.Write(buf)
|
||||
}
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == session.Protocol {
|
||||
drivePath, err := model.GetDrivePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Destination
|
||||
dst, err := os.Create(path.Join(drivePath, remoteFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
// Copy
|
||||
if _, err = io.Copy(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func SessionDownloadEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
session, err := model.FindSessionById(sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//remoteDir := c.Query("dir")
|
||||
remoteFile := c.QueryParam("file")
|
||||
|
||||
if "ssh" == session.Protocol {
|
||||
tun, ok := config.Store.Get(sessionId)
|
||||
if !ok {
|
||||
return errors.New("获取sftp客户端失败")
|
||||
}
|
||||
|
||||
dstFile, err := tun.SftpClient.Open(remoteFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer dstFile.Close()
|
||||
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", remoteFile))
|
||||
|
||||
var buff bytes.Buffer
|
||||
if _, err := dstFile.WriteTo(&buff); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(buff.Bytes()))
|
||||
} else if "rdp" == session.Protocol {
|
||||
drivePath, err := model.GetDrivePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.File(path.Join(drivePath, remoteFile))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
IsDir bool `json:"isDir"`
|
||||
Mode string `json:"mode"`
|
||||
IsLink bool `json:"isLink"`
|
||||
}
|
||||
|
||||
func SessionLsEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
session, err := model.FindSessionById(sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remoteDir := c.QueryParam("dir")
|
||||
if "ssh" == session.Protocol {
|
||||
tun, ok := config.Store.Get(sessionId)
|
||||
if !ok {
|
||||
return errors.New("获取sftp客户端失败")
|
||||
}
|
||||
|
||||
fileInfos, err := tun.SftpClient.ReadDir(remoteDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var files = make([]File, 0)
|
||||
for i := range fileInfos {
|
||||
file := File{
|
||||
Name: fileInfos[i].Name(),
|
||||
Path: path.Join(remoteDir, fileInfos[i].Name()),
|
||||
IsDir: fileInfos[i].IsDir(),
|
||||
Mode: fileInfos[i].Mode().String(),
|
||||
IsLink: fileInfos[i].Mode()&os.ModeSymlink == os.ModeSymlink,
|
||||
}
|
||||
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
return Success(c, files)
|
||||
} else if "rdp" == session.Protocol {
|
||||
drivePath, err := model.GetDrivePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileInfos, err := ioutil.ReadDir(path.Join(drivePath, remoteDir))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var files = make([]File, 0)
|
||||
for i := range fileInfos {
|
||||
file := File{
|
||||
Name: fileInfos[i].Name(),
|
||||
Path: path.Join(remoteDir, fileInfos[i].Name()),
|
||||
IsDir: fileInfos[i].IsDir(),
|
||||
Mode: fileInfos[i].Mode().String(),
|
||||
IsLink: fileInfos[i].Mode()&os.ModeSymlink == os.ModeSymlink,
|
||||
}
|
||||
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
return Success(c, files)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func SessionMkDirEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
session, err := model.FindSessionById(sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remoteDir := c.QueryParam("dir")
|
||||
if "ssh" == session.Protocol {
|
||||
tun, ok := config.Store.Get(sessionId)
|
||||
if !ok {
|
||||
return errors.New("获取sftp客户端失败")
|
||||
}
|
||||
if err := tun.SftpClient.Mkdir(remoteDir); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == session.Protocol {
|
||||
drivePath, err := model.GetDrivePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Join(drivePath, remoteDir), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SessionRmDirEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
session, err := model.FindSessionById(sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remoteDir := c.QueryParam("dir")
|
||||
if "ssh" == session.Protocol {
|
||||
tun, ok := config.Store.Get(sessionId)
|
||||
if !ok {
|
||||
return errors.New("获取sftp客户端失败")
|
||||
}
|
||||
fileInfos, err := tun.SftpClient.ReadDir(remoteDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range fileInfos {
|
||||
if err := tun.SftpClient.Remove(path.Join(remoteDir, fileInfos[i].Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tun.SftpClient.RemoveDirectory(remoteDir); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == session.Protocol {
|
||||
drivePath, err := model.GetDrivePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(path.Join(drivePath, remoteDir)); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SessionRmEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
session, err := model.FindSessionById(sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remoteFile := c.QueryParam("file")
|
||||
if "ssh" == session.Protocol {
|
||||
tun, ok := config.Store.Get(sessionId)
|
||||
if !ok {
|
||||
return errors.New("获取sftp客户端失败")
|
||||
}
|
||||
if err := tun.SftpClient.Remove(remoteFile); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
} else if "rdp" == session.Protocol {
|
||||
drivePath, err := model.GetDrivePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Remove(path.Join(drivePath, remoteFile)); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
175
pkg/api/ssh.go
Normal file
175
pkg/api/ssh.go
Normal file
@ -0,0 +1,175 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"next-terminal/pkg/model"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var UpGrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
Subprotocols: []string{"guacamole"},
|
||||
}
|
||||
|
||||
type NextWriter struct {
|
||||
b bytes.Buffer
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *NextWriter) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.b.Write(p)
|
||||
}
|
||||
|
||||
func (w *NextWriter) Read() ([]byte, int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
p := w.b.Bytes()
|
||||
buf := make([]byte, len(p))
|
||||
read, err := w.b.Read(buf)
|
||||
w.b.Reset()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return buf, read, err
|
||||
}
|
||||
|
||||
func SSHEndpoint(c echo.Context) error {
|
||||
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
assetId := c.QueryParam("assetId")
|
||||
width, _ := strconv.Atoi(c.QueryParam("width"))
|
||||
height, _ := strconv.Atoi(c.QueryParam("height"))
|
||||
|
||||
asset, err := model.FindAssetById(assetId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if asset.AccountType == "credential" {
|
||||
credential, err := model.FindCredentialById(asset.CredentialId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
asset.Username = credential.Username
|
||||
asset.Password = credential.Password
|
||||
}
|
||||
|
||||
config := &ssh.ClientConfig{
|
||||
Timeout: 1 * time.Second,
|
||||
User: asset.Username,
|
||||
Auth: []ssh.AuthMethod{ssh.Password(asset.Password)},
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", asset.IP, asset.Port)
|
||||
|
||||
sshClient, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
session, err := sshClient.NewSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
modes := ssh.TerminalModes{
|
||||
ssh.ECHO: 1,
|
||||
ssh.TTY_OP_ISPEED: 14400,
|
||||
ssh.TTY_OP_OSPEED: 14400,
|
||||
}
|
||||
|
||||
if err := session.RequestPty("xterm", height, width, modes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var b NextWriter
|
||||
session.Stdout = &b
|
||||
session.Stderr = &b
|
||||
|
||||
stdinPipe, err := session.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := session.Shell(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
||||
for true {
|
||||
p, n, err := b.Read()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if n > 0 {
|
||||
WriteByteMessage(ws, p)
|
||||
}
|
||||
time.Sleep(time.Duration(100) * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
for true {
|
||||
_, message, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
_, err = stdinPipe.Write(message)
|
||||
if err != nil {
|
||||
log.Println("Tunnel write:", err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func WriteMessage(ws *websocket.Conn, message string) {
|
||||
WriteByteMessage(ws, []byte(message))
|
||||
}
|
||||
|
||||
func WriteByteMessage(ws *websocket.Conn, p []byte) {
|
||||
err := ws.WriteMessage(websocket.TextMessage, p)
|
||||
if err != nil {
|
||||
log.Println("write:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func CreateSftpClient(username, password, ip string, port int) (sftpClient *sftp.Client, err error) {
|
||||
clientConfig := &ssh.ClientConfig{
|
||||
Timeout: 1 * time.Second,
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{ssh.Password(password)},
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", ip, port)
|
||||
|
||||
sshClient, err := ssh.Dial("tcp", addr, clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sftp.NewClient(sshClient)
|
||||
}
|
33
pkg/api/test/ps.go
Normal file
33
pkg/api/test/ps.go
Normal file
@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
//v, _ := mem.VirtualMemory()
|
||||
//c, _ := cpu.Info()
|
||||
//cc, _ := cpu.Percent(time.Second, false)
|
||||
//d, _ := disk.Usage("/")
|
||||
//n, _ := host.Info()
|
||||
//nv, _ := net.IOCounters(true)
|
||||
//boottime, _ := host.BootTime()
|
||||
//btime := time.Unix(int64(boottime), 0).Format("2006-01-02 15:04:05")
|
||||
//
|
||||
//fmt.Printf(" Mem : %v MB Free: %v MB Used:%v Usage:%f%%\n", v.Total/1024/1024, v.Available/1024/1024, v.Used/1024/1024, v.UsedPercent)
|
||||
//if len(c) > 1 {
|
||||
// for _, sub_cpu := range c {
|
||||
// modelname := sub_cpu.ModelName
|
||||
// cores := sub_cpu.Cores
|
||||
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
|
||||
// }
|
||||
//} else {
|
||||
// sub_cpu := c[0]
|
||||
// modelname := sub_cpu.ModelName
|
||||
// cores := sub_cpu.Cores
|
||||
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
|
||||
//
|
||||
//}
|
||||
//fmt.Printf(" Network: %v bytes / %v bytes\n", nv[0].BytesRecv, nv[0].BytesSent)
|
||||
//fmt.Printf(" SystemBoot:%v\n", btime)
|
||||
//fmt.Printf(" CPU Used : used %f%% \n", cc[0])
|
||||
//fmt.Printf(" HD : %v GB Free: %v GB Usage:%f%%\n", d.Total/1024/1024/1024, d.Free/1024/1024/1024, d.UsedPercent)
|
||||
//fmt.Printf(" OS : %v(%v) %v \n", n.Platform, n.PlatformFamily, n.PlatformVersion)
|
||||
//fmt.Printf(" Hostname : %v \n", n.Hostname)
|
||||
}
|
158
pkg/api/tunnel.go
Normal file
158
pkg/api/tunnel.go
Normal file
@ -0,0 +1,158 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"next-terminal/pkg/config"
|
||||
"next-terminal/pkg/guacd"
|
||||
"next-terminal/pkg/model"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/pkg/sftp"
|
||||
"log"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func TunEndpoint(c echo.Context) error {
|
||||
|
||||
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
width := c.QueryParam("width")
|
||||
height := c.QueryParam("height")
|
||||
sessionId := c.QueryParam("sessionId")
|
||||
connectionId := c.QueryParam("connectionId")
|
||||
|
||||
intWidth, _ := strconv.Atoi(width)
|
||||
intHeight, _ := strconv.Atoi(height)
|
||||
|
||||
configuration := guacd.NewConfiguration()
|
||||
configuration.SetParameter("width", width)
|
||||
configuration.SetParameter("height", height)
|
||||
|
||||
propertyMap := model.FindAllPropertiesMap()
|
||||
|
||||
for name := range propertyMap {
|
||||
|
||||
if name == model.GuacdFontSize {
|
||||
fontSize, _ := strconv.Atoi(propertyMap[name])
|
||||
fontSize = fontSize * 2
|
||||
configuration.SetParameter(name, strconv.Itoa(fontSize))
|
||||
} else {
|
||||
configuration.SetParameter(name, propertyMap[name])
|
||||
}
|
||||
}
|
||||
|
||||
var session model.Session
|
||||
var sftpClient *sftp.Client
|
||||
|
||||
if len(connectionId) > 0 {
|
||||
session, err = model.FindSessionByConnectionId(connectionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configuration.ConnectionID = connectionId
|
||||
} else {
|
||||
session, err = model.FindSessionById(sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configuration.Protocol = session.Protocol
|
||||
switch configuration.Protocol {
|
||||
case "rdp":
|
||||
configuration.SetParameter("username", session.Username)
|
||||
configuration.SetParameter("password", session.Password)
|
||||
|
||||
configuration.SetParameter("security", "any")
|
||||
configuration.SetParameter("ignore-cert", "true")
|
||||
configuration.SetParameter("create-drive-path", "true")
|
||||
|
||||
configuration.SetParameter("dpi", "96")
|
||||
configuration.SetParameter("resize-method", "reconnect")
|
||||
configuration.SetParameter("enable-sftp", "")
|
||||
break
|
||||
case "ssh":
|
||||
configuration.SetParameter("username", session.Username)
|
||||
configuration.SetParameter("password", session.Password)
|
||||
|
||||
sftpClient, err = CreateSftpClient(session.Username, session.Password, session.IP, session.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
case "vnc":
|
||||
configuration.SetParameter("password", session.Password)
|
||||
configuration.SetParameter("enable-sftp", "")
|
||||
break
|
||||
case "telnet":
|
||||
configuration.SetParameter("username", session.Username)
|
||||
configuration.SetParameter("password", session.Password)
|
||||
configuration.SetParameter("enable-sftp", "")
|
||||
break
|
||||
}
|
||||
|
||||
configuration.SetParameter("hostname", session.IP)
|
||||
configuration.SetParameter("port", strconv.Itoa(session.Port))
|
||||
}
|
||||
|
||||
addr := propertyMap[model.GuacdHost] + ":" + propertyMap[model.GuacdPort]
|
||||
tunnel, err := guacd.NewTunnel(addr, configuration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("=====================================================\n")
|
||||
fmt.Printf("connect to %v with config: %+v\n", addr, configuration)
|
||||
fmt.Printf("=====================================================\n")
|
||||
|
||||
tun := config.Tun{
|
||||
Tun: tunnel,
|
||||
SftpClient: sftpClient,
|
||||
}
|
||||
|
||||
config.Store.Set(sessionId, tun)
|
||||
|
||||
if len(session.ConnectionId) == 0 {
|
||||
session.ConnectionId = tunnel.UUID
|
||||
session.Width = intWidth
|
||||
session.Height = intHeight
|
||||
|
||||
model.UpdateSessionById(&session, sessionId)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for true {
|
||||
instruction, err := tunnel.Read()
|
||||
if err != nil {
|
||||
CloseSession(sessionId, tun)
|
||||
log.Printf("WS读取异常: %v", err)
|
||||
break
|
||||
}
|
||||
//fmt.Printf("<= %v \n", string(instruction))
|
||||
err = ws.WriteMessage(websocket.TextMessage, instruction)
|
||||
if err != nil {
|
||||
CloseSession(sessionId, tun)
|
||||
log.Printf("WS写入异常: %v", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for true {
|
||||
_, message, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
CloseSession(sessionId, tun)
|
||||
log.Printf("Tunnel读取异常: %v", err)
|
||||
break
|
||||
}
|
||||
_, err = tunnel.WriteAndFlush(message)
|
||||
if err != nil {
|
||||
CloseSession(sessionId, tun)
|
||||
log.Printf("Tunnel写入异常: %v", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
83
pkg/api/user.go
Normal file
83
pkg/api/user.go
Normal file
@ -0,0 +1,83 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func UserCreateEndpoint(c echo.Context) error {
|
||||
var item model.User
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pass []byte
|
||||
var err error
|
||||
if pass, err = utils.Encoder.Encode([]byte("admin")); err != nil {
|
||||
return err
|
||||
}
|
||||
item.Password = string(pass)
|
||||
|
||||
item.ID = utils.UUID()
|
||||
item.Created = utils.NowJsonTime()
|
||||
|
||||
if err := model.CreateNewUser(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
func UserPagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
username := c.QueryParam("username")
|
||||
nickname := c.QueryParam("nickname")
|
||||
|
||||
items, total, err := model.FindPageUser(pageIndex, pageSize, username, nickname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, H{
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func UserUpdateEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
var item model.User
|
||||
if err := c.Bind(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model.UpdateUserById(&item, id)
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func UserDeleteEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
split := strings.Split(id, ",")
|
||||
for i := range split {
|
||||
model.DeleteUserById(split[i])
|
||||
}
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func UserGetEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
item, err := model.FindUserById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Success(c, item)
|
||||
}
|
14
pkg/config/config.go
Normal file
14
pkg/config/config.go
Normal file
@ -0,0 +1,14 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/patrickmn/go-cache"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var DB *gorm.DB
|
||||
|
||||
var Cache *cache.Cache
|
||||
|
||||
var NextTerminal *NextTerminalConfig
|
||||
|
||||
var Store *TunStore
|
31
pkg/config/next-terminal.go
Normal file
31
pkg/config/next-terminal.go
Normal file
@ -0,0 +1,31 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/viper"
|
||||
"log"
|
||||
)
|
||||
|
||||
type NextTerminalConfig struct {
|
||||
Dsn string
|
||||
Addr string
|
||||
}
|
||||
|
||||
func SetupConfig() *NextTerminalConfig {
|
||||
|
||||
viper.SetConfigName("next-terminal")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath("/etc/next-terminal/")
|
||||
viper.AddConfigPath("$HOME/.next-terminal")
|
||||
viper.AddConfigPath(".")
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var config = &NextTerminalConfig{
|
||||
Dsn: viper.GetString("next-terminal.dsn"),
|
||||
Addr: viper.GetString("next-terminal.addr"),
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
37
pkg/config/store.go
Normal file
37
pkg/config/store.go
Normal file
@ -0,0 +1,37 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/pkg/sftp"
|
||||
"next-terminal/pkg/guacd"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Tun struct {
|
||||
Tun guacd.Tunnel
|
||||
SftpClient *sftp.Client
|
||||
}
|
||||
|
||||
type TunStore struct {
|
||||
m sync.Map
|
||||
}
|
||||
|
||||
func (s *TunStore) Set(k string, v Tun) {
|
||||
s.m.Store(k, v)
|
||||
}
|
||||
|
||||
func (s *TunStore) Del(k string) {
|
||||
s.m.Delete(k)
|
||||
}
|
||||
|
||||
func (s *TunStore) Get(k string) (item Tun, ok bool) {
|
||||
value, ok := s.m.Load(k)
|
||||
if ok {
|
||||
return value.(Tun), true
|
||||
}
|
||||
return item, false
|
||||
}
|
||||
|
||||
func NewStore() *TunStore {
|
||||
store := TunStore{sync.Map{}}
|
||||
return &store
|
||||
}
|
227
pkg/guacd/guacd.go
Normal file
227
pkg/guacd/guacd.go
Normal file
@ -0,0 +1,227 @@
|
||||
package guacd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const Delimiter = ';'
|
||||
const Version = "VERSION_1_1_0"
|
||||
|
||||
type Configuration struct {
|
||||
ConnectionID string
|
||||
Protocol string
|
||||
Parameters map[string]string
|
||||
}
|
||||
|
||||
func NewConfiguration() (ret Configuration) {
|
||||
ret.Parameters = make(map[string]string)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (opt *Configuration) SetParameter(name, value string) {
|
||||
opt.Parameters[name] = value
|
||||
}
|
||||
|
||||
func (opt *Configuration) UnSetParameter(name string) {
|
||||
delete(opt.Parameters, name)
|
||||
}
|
||||
|
||||
func (opt *Configuration) GetParameter(name string) string {
|
||||
return opt.Parameters[name]
|
||||
}
|
||||
|
||||
type Instruction struct {
|
||||
Opcode string
|
||||
Args []string
|
||||
ProtocolForm string
|
||||
}
|
||||
|
||||
func NewInstruction(opcode string, args ...string) (ret Instruction) {
|
||||
ret.Opcode = opcode
|
||||
ret.Args = args
|
||||
return ret
|
||||
}
|
||||
|
||||
func (opt *Instruction) String() string {
|
||||
if len(opt.ProtocolForm) > 0 {
|
||||
return opt.ProtocolForm
|
||||
}
|
||||
|
||||
opt.ProtocolForm = fmt.Sprintf("%d.%s", len(opt.Opcode), opt.Opcode)
|
||||
for _, value := range opt.Args {
|
||||
opt.ProtocolForm += fmt.Sprintf(",%d.%s", len(value), value)
|
||||
}
|
||||
opt.ProtocolForm += string(Delimiter)
|
||||
return opt.ProtocolForm
|
||||
}
|
||||
|
||||
func (opt *Instruction) Parse(content string) Instruction {
|
||||
if strings.LastIndex(content, ";") > 0 {
|
||||
content = strings.TrimRight(content, ";")
|
||||
}
|
||||
messages := strings.Split(content, ",")
|
||||
|
||||
var args = make([]string, len(messages))
|
||||
for i := range messages {
|
||||
lm := strings.Split(messages[i], ".")
|
||||
args[i] = lm[1]
|
||||
}
|
||||
return NewInstruction(args[0], args[1:]...)
|
||||
}
|
||||
|
||||
type Tunnel struct {
|
||||
rw *bufio.ReadWriter
|
||||
conn net.Conn
|
||||
UUID string
|
||||
Config Configuration
|
||||
IsOpen bool
|
||||
}
|
||||
|
||||
func NewTunnel(address string, config Configuration) (ret Tunnel, err error) {
|
||||
|
||||
conn, err := net.Dial("tcp", address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ret.conn = conn
|
||||
ret.rw = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
||||
ret.Config = config
|
||||
|
||||
selectArg := config.ConnectionID
|
||||
if selectArg == "" {
|
||||
selectArg = config.Protocol
|
||||
}
|
||||
|
||||
if err := ret.WriteInstructionAndFlush(NewInstruction("select", selectArg)); err != nil {
|
||||
return Tunnel{}, err
|
||||
}
|
||||
|
||||
args, err := ret.expect("args")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
width := config.GetParameter("width")
|
||||
height := config.GetParameter("height")
|
||||
// send size
|
||||
if err := ret.WriteInstructionAndFlush(NewInstruction("size", width, height, "96")); err != nil {
|
||||
return Tunnel{}, err
|
||||
}
|
||||
|
||||
if err := ret.WriteInstructionAndFlush(NewInstruction("audio")); err != nil {
|
||||
return Tunnel{}, err
|
||||
}
|
||||
if err := ret.WriteInstructionAndFlush(NewInstruction("video")); err != nil {
|
||||
return Tunnel{}, err
|
||||
}
|
||||
if err := ret.WriteInstructionAndFlush(NewInstruction("image")); err != nil {
|
||||
return Tunnel{}, err
|
||||
}
|
||||
|
||||
if err := ret.WriteInstructionAndFlush(NewInstruction("timezone", "Asia/Shanghai")); err != nil {
|
||||
return Tunnel{}, err
|
||||
}
|
||||
|
||||
parameters := make([]string, len(args.Args))
|
||||
for i := range args.Args {
|
||||
argName := args.Args[i]
|
||||
if strings.Contains(argName, "VERSION") {
|
||||
parameters[i] = Version
|
||||
continue
|
||||
}
|
||||
parameters[i] = config.GetParameter(argName)
|
||||
}
|
||||
// send connect
|
||||
if err := ret.WriteInstructionAndFlush(NewInstruction("connect", parameters...)); err != nil {
|
||||
return Tunnel{}, err
|
||||
}
|
||||
|
||||
ready, err := ret.expect("ready")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ready.Args) == 0 {
|
||||
return ret, errors.New("no connection id received")
|
||||
}
|
||||
|
||||
ret.UUID = ready.Args[0]
|
||||
ret.IsOpen = true
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (opt *Tunnel) WriteInstructionAndFlush(instruction Instruction) error {
|
||||
if _, err := opt.WriteAndFlush([]byte(instruction.String())); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opt *Tunnel) WriteInstruction(instruction Instruction) error {
|
||||
if _, err := opt.Write([]byte(instruction.String())); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opt *Tunnel) WriteAndFlush(p []byte) (int, error) {
|
||||
//fmt.Printf("-> %v \n", string(p))
|
||||
nn, err := opt.rw.Write(p)
|
||||
if err != nil {
|
||||
return nn, err
|
||||
}
|
||||
err = opt.rw.Flush()
|
||||
if err != nil {
|
||||
return nn, err
|
||||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
func (opt *Tunnel) Write(p []byte) (int, error) {
|
||||
//fmt.Printf("-> %v \n", string(p))
|
||||
nn, err := opt.rw.Write(p)
|
||||
if err != nil {
|
||||
return nn, err
|
||||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
func (opt *Tunnel) Flush() error {
|
||||
return opt.rw.Flush()
|
||||
}
|
||||
|
||||
func (opt *Tunnel) ReadInstruction() (instruction Instruction, err error) {
|
||||
msg, err := opt.rw.ReadString(Delimiter)
|
||||
if err != nil {
|
||||
return instruction, err
|
||||
}
|
||||
fmt.Printf("<- %v \n", msg)
|
||||
return instruction.Parse(msg), err
|
||||
}
|
||||
|
||||
func (opt *Tunnel) Read() ([]byte, error) {
|
||||
return opt.rw.ReadBytes(Delimiter)
|
||||
}
|
||||
|
||||
func (opt *Tunnel) expect(opcode string) (instruction Instruction, err error) {
|
||||
instruction, err = opt.ReadInstruction()
|
||||
if err != nil {
|
||||
return instruction, err
|
||||
}
|
||||
|
||||
if opcode != instruction.Opcode {
|
||||
msg := fmt.Sprintf(`expected "%s" instruction but instead received "%s"`, opcode, instruction.Opcode)
|
||||
return instruction, errors.New(msg)
|
||||
}
|
||||
return instruction, nil
|
||||
}
|
||||
|
||||
func (opt *Tunnel) Close() error {
|
||||
opt.IsOpen = false
|
||||
return opt.conn.Close()
|
||||
}
|
199
pkg/handle/runner.go
Normal file
199
pkg/handle/runner.go
Normal file
@ -0,0 +1,199 @@
|
||||
package handle
|
||||
|
||||
import (
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func RunTicker() {
|
||||
var ch chan int
|
||||
//定时任务
|
||||
ticker := time.NewTicker(time.Minute * 5)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
items, _ := model.FindAllAsset()
|
||||
|
||||
for i := range items {
|
||||
item := items[i]
|
||||
active := utils.Tcping(item.IP, item.Port)
|
||||
|
||||
asset := model.Asset{
|
||||
Active: active,
|
||||
}
|
||||
|
||||
model.UpdateAssetById(&asset, item.ID)
|
||||
}
|
||||
}
|
||||
ch <- 1
|
||||
}()
|
||||
<-ch
|
||||
}
|
||||
|
||||
func RunDataFix() {
|
||||
sessions, _ := model.FindSessionByStatus(model.Connected)
|
||||
if sessions == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range sessions {
|
||||
session := model.Session{
|
||||
Status: model.Disconnected,
|
||||
DisconnectedTime: utils.NowJsonTime(),
|
||||
}
|
||||
|
||||
model.UpdateSessionById(&session, sessions[i].ID)
|
||||
}
|
||||
}
|
||||
|
||||
func InitProperties() {
|
||||
propertyMap := model.FindAllPropertiesMap()
|
||||
|
||||
if len(propertyMap[model.GuacdHost]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdHost,
|
||||
Value: "127.0.0.1",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdPort]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdPort,
|
||||
Value: "4822",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdDriveName]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdDriveName,
|
||||
Value: "File-System",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdDrivePath]) == 0 {
|
||||
|
||||
path, _ := os.Getwd()
|
||||
|
||||
property := model.Property{
|
||||
Name: model.GuacdDrivePath,
|
||||
Value: path + "/drive/",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdFontName]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdFontName,
|
||||
Value: "menlo",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdFontSize]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdFontSize,
|
||||
Value: "12",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdColorScheme]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdColorScheme,
|
||||
Value: "gray-black",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdEnableSftp]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdEnableSftp,
|
||||
Value: "true",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdEnableDrive]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdEnableDrive,
|
||||
Value: "true",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdEnableWallpaper]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdEnableWallpaper,
|
||||
Value: "false",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdEnableTheming]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdEnableTheming,
|
||||
Value: "false",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdEnableFontSmoothing]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdEnableFontSmoothing,
|
||||
Value: "false",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdEnableFullWindowDrag]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdEnableFullWindowDrag,
|
||||
Value: "false",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdEnableDesktopComposition]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdEnableDesktopComposition,
|
||||
Value: "false",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdEnableMenuAnimations]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdEnableMenuAnimations,
|
||||
Value: "false",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdDisableBitmapCaching]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdDisableBitmapCaching,
|
||||
Value: "false",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdDisableOffscreenCaching]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdDisableOffscreenCaching,
|
||||
Value: "false",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
|
||||
if len(propertyMap[model.GuacdDisableGlyphCaching]) == 0 {
|
||||
property := model.Property{
|
||||
Name: model.GuacdDisableGlyphCaching,
|
||||
Value: "false",
|
||||
}
|
||||
_ = model.CreateNewProperty(&property)
|
||||
}
|
||||
}
|
86
pkg/model/asset.go
Normal file
86
pkg/model/asset.go
Normal file
@ -0,0 +1,86 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"next-terminal/pkg/config"
|
||||
"next-terminal/pkg/utils"
|
||||
)
|
||||
|
||||
type Asset struct {
|
||||
ID string `gorm:"primary_key " json:"id"`
|
||||
Name string `json:"name"`
|
||||
IP string `json:"ip"`
|
||||
Protocol string `json:"protocol"`
|
||||
Port int `json:"port"`
|
||||
AccountType string `json:"accountType"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
CredentialId string `json:"credentialId"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
Passphrase string `json:"passphrase"`
|
||||
Description string `json:"description"`
|
||||
Active bool `json:"active"`
|
||||
Created utils.JsonTime `json:"created"`
|
||||
}
|
||||
|
||||
func (r *Asset) TableName() string {
|
||||
return "assets"
|
||||
}
|
||||
|
||||
func FindAllAsset() (o []Asset, err error) {
|
||||
err = config.DB.Find(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func FindAssetByConditions(protocol string) (o []Asset, err error) {
|
||||
db := config.DB
|
||||
|
||||
if len(protocol) > 0 {
|
||||
db = db.Where("protocol = ?", protocol)
|
||||
}
|
||||
err = db.Find(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func FindPageAsset(pageIndex, pageSize int, name, protocol string) (o []Asset, total int64, err error) {
|
||||
db := config.DB
|
||||
if len(name) > 0 {
|
||||
db = db.Where("name like ?", "%"+name+"%")
|
||||
}
|
||||
|
||||
if len(protocol) > 0 {
|
||||
db = db.Where("protocol = ?", protocol)
|
||||
}
|
||||
|
||||
err = db.Order("created desc").Find(&o).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Count(&total).Error
|
||||
|
||||
if o == nil {
|
||||
o = make([]Asset, 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CreateNewAsset(o *Asset) (err error) {
|
||||
if err = config.DB.Create(o).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindAssetById(id string) (o Asset, err error) {
|
||||
err = config.DB.Where("id = ?", id).First(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func UpdateAssetById(o *Asset, id string) {
|
||||
o.ID = id
|
||||
config.DB.Updates(o)
|
||||
}
|
||||
|
||||
func DeleteAssetById(id string) {
|
||||
config.DB.Where("id = ?", id).Delete(&Asset{})
|
||||
}
|
||||
|
||||
func CountAsset() (total int64, err error) {
|
||||
err = config.DB.Find(&Asset{}).Count(&total).Error
|
||||
return
|
||||
}
|
56
pkg/model/command.go
Normal file
56
pkg/model/command.go
Normal file
@ -0,0 +1,56 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"next-terminal/pkg/config"
|
||||
"next-terminal/pkg/utils"
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
ID string `gorm:"primary_key" json:"id"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
Created utils.JsonTime `json:"created"`
|
||||
}
|
||||
|
||||
func (r *Command) TableName() string {
|
||||
return "commands"
|
||||
}
|
||||
|
||||
func FindPageCommand(pageIndex, pageSize int, name, content string) (o []Command, total int64, err error) {
|
||||
|
||||
db := config.DB
|
||||
if len(name) > 0 {
|
||||
db = db.Where("name like ?", "%"+name+"%")
|
||||
}
|
||||
|
||||
if len(content) > 0 {
|
||||
db = db.Where("content like ?", "%"+content+"%")
|
||||
}
|
||||
|
||||
err = db.Find(&o).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Count(&total).Error
|
||||
if o == nil {
|
||||
o = make([]Command, 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CreateNewCommand(o *Command) (err error) {
|
||||
if err = config.DB.Create(o).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindCommandById(id string) (o Command, err error) {
|
||||
err = config.DB.Where("id = ?", id).First(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func UpdateCommandById(o *Command, id string) {
|
||||
o.ID = id
|
||||
config.DB.Updates(o)
|
||||
}
|
||||
|
||||
func DeleteCommandById(id string) {
|
||||
config.DB.Where("id = ?", id).Delete(&Command{})
|
||||
}
|
63
pkg/model/credential.go
Normal file
63
pkg/model/credential.go
Normal file
@ -0,0 +1,63 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"next-terminal/pkg/config"
|
||||
"next-terminal/pkg/utils"
|
||||
)
|
||||
|
||||
type Credential struct {
|
||||
ID string `gorm:"primary_key" json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Created utils.JsonTime `json:"created"`
|
||||
}
|
||||
|
||||
func (r *Credential) TableName() string {
|
||||
return "credentials"
|
||||
}
|
||||
|
||||
func FindAllCredential() (o []Credential, err error) {
|
||||
err = config.DB.Find(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func FindPageCredential(pageIndex, pageSize int, name string) (o []Credential, total int64, err error) {
|
||||
db := config.DB
|
||||
if len(name) > 0 {
|
||||
db = db.Where("name like ?", "%"+name+"%")
|
||||
}
|
||||
|
||||
err = db.Order("created desc").Find(&o).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Count(&total).Error
|
||||
if o == nil {
|
||||
o = make([]Credential, 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CreateNewCredential(o *Credential) (err error) {
|
||||
if err = config.DB.Create(o).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindCredentialById(id string) (o Credential, err error) {
|
||||
|
||||
err = config.DB.Where("id = ?", id).First(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func UpdateCredentialById(o *Credential, id string) {
|
||||
o.ID = id
|
||||
config.DB.Updates(o)
|
||||
}
|
||||
|
||||
func DeleteCredentialById(id string) {
|
||||
config.DB.Where("id = ?", id).Delete(&Credential{})
|
||||
}
|
||||
|
||||
func CountCredential() (total int64, err error) {
|
||||
err = config.DB.Find(&Credential{}).Count(&total).Error
|
||||
return
|
||||
}
|
78
pkg/model/property.go
Normal file
78
pkg/model/property.go
Normal file
@ -0,0 +1,78 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"next-terminal/pkg/config"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
GuacdHost = "host"
|
||||
GuacdPort = "port"
|
||||
|
||||
GuacdFontName = "font-name"
|
||||
GuacdFontSize = "font-size"
|
||||
GuacdColorScheme = "color-scheme"
|
||||
GuacdEnableSftp = "enable-sftp"
|
||||
|
||||
GuacdEnableDrive = "enable-drive"
|
||||
GuacdDriveName = "drive-name"
|
||||
GuacdDrivePath = "drive-path"
|
||||
GuacdEnableWallpaper = "enable-wallpaper"
|
||||
GuacdEnableTheming = "enable-theming"
|
||||
GuacdEnableFontSmoothing = "enable-font-smoothing"
|
||||
GuacdEnableFullWindowDrag = "enable-full-window-drag"
|
||||
GuacdEnableDesktopComposition = "enable-desktop-composition"
|
||||
GuacdEnableMenuAnimations = "enable-menu-animations"
|
||||
GuacdDisableBitmapCaching = "disable-bitmap-caching"
|
||||
GuacdDisableOffscreenCaching = "disable-offscreen-caching"
|
||||
GuacdDisableGlyphCaching = "disable-glyph-caching"
|
||||
)
|
||||
|
||||
type Property struct {
|
||||
Name string `gorm:"primary_key" json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (r *Property) TableName() string {
|
||||
return "properties"
|
||||
}
|
||||
|
||||
func FindAllProperties() (o []Property) {
|
||||
if config.DB.Find(&o).Error != nil {
|
||||
return nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CreateNewProperty(o *Property) (err error) {
|
||||
err = config.DB.Create(o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func UpdatePropertyByName(o *Property, name string) {
|
||||
o.Name = name
|
||||
config.DB.Updates(o)
|
||||
}
|
||||
|
||||
func FindPropertyByName(name string) (o Property, err error) {
|
||||
err = config.DB.Where("name = ?", name).First(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func FindAllPropertiesMap() map[string]string {
|
||||
properties := FindAllProperties()
|
||||
propertyMap := make(map[string]string)
|
||||
for i := range properties {
|
||||
propertyMap[properties[i].Name] = properties[i].Value
|
||||
}
|
||||
return propertyMap
|
||||
}
|
||||
|
||||
func GetDrivePath() (string, error) {
|
||||
propertiesMap := FindAllPropertiesMap()
|
||||
drivePath := propertiesMap[GuacdDrivePath]
|
||||
if len(drivePath) == 0 {
|
||||
return "", errors.New("获取RDP挂载目录失败")
|
||||
}
|
||||
return drivePath, nil
|
||||
}
|
138
pkg/model/session.go
Normal file
138
pkg/model/session.go
Normal file
@ -0,0 +1,138 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"next-terminal/pkg/config"
|
||||
"next-terminal/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
Connected = "connected"
|
||||
Disconnected = "disconnected"
|
||||
NoConnect = "no_connect"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
ID string `gorm:"primary_key" json:"id"`
|
||||
Protocol string `json:"protocol"`
|
||||
IP string `json:"ip"`
|
||||
Port int `json:"port"`
|
||||
ConnectionId string `json:"connectionId"`
|
||||
AssetId string `json:"assetId"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Creator string `json:"creator"`
|
||||
ClientIP string `json:"clientIp"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Status string `json:"status"`
|
||||
ConnectedTime utils.JsonTime `json:"connectedTime"`
|
||||
DisconnectedTime utils.JsonTime `json:"disconnectedTime"`
|
||||
}
|
||||
|
||||
func (r *Session) TableName() string {
|
||||
return "sessions"
|
||||
}
|
||||
|
||||
type SessionVo struct {
|
||||
ID string `json:"id"`
|
||||
Protocol string `json:"protocol"`
|
||||
IP string `json:"ip"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
ConnectionId string `json:"connectionId"`
|
||||
AssetId string `json:"assetId"`
|
||||
Creator string `json:"creator"`
|
||||
ClientIP string `json:"clientIp"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Status string `json:"status"`
|
||||
ConnectedTime utils.JsonTime `json:"connectedTime"`
|
||||
DisconnectedTime utils.JsonTime `json:"disconnectedTime"`
|
||||
AssetName string `json:"assetName"`
|
||||
CreatorName string `json:"creatorName"`
|
||||
}
|
||||
|
||||
func FindPageSession(pageIndex, pageSize int, status, userId, clientIp, assetId, protocol string) (results []SessionVo, total int64, err error) {
|
||||
|
||||
db := config.DB
|
||||
var params []interface{}
|
||||
|
||||
params = append(params, status)
|
||||
|
||||
itemSql := "SELECT s.id, s.protocol, s.connection_id, s.asset_id, s.creator, s.client_ip, s.width, s.height, s.ip, s.port, s.username, s.status, s.connected_time, s.disconnected_time, a.name AS asset_name, u.nickname AS creator_name FROM sessions s LEFT JOIN assets a ON s.asset_id = a.id LEFT JOIN users u ON s.creator = u.id WHERE s.STATUS = ? "
|
||||
countSql := "select count(*) from sessions as s where s.status = ? "
|
||||
|
||||
if len(userId) > 0 {
|
||||
itemSql += " and s.creator = ?"
|
||||
countSql += " and s.creator = ?"
|
||||
params = append(params, userId)
|
||||
}
|
||||
|
||||
if len(clientIp) > 0 {
|
||||
itemSql += " and s.client_ip like ?"
|
||||
countSql += " and s.client_ip like ?"
|
||||
params = append(params, "%"+clientIp+"%")
|
||||
}
|
||||
|
||||
if len(assetId) > 0 {
|
||||
itemSql += " and s.asset_id = ?"
|
||||
countSql += " and s.asset_id = ?"
|
||||
params = append(params, assetId)
|
||||
}
|
||||
|
||||
if len(protocol) > 0 {
|
||||
itemSql += " and s.protocol = ?"
|
||||
countSql += " and s.protocol = ?"
|
||||
params = append(params, protocol)
|
||||
}
|
||||
|
||||
params = append(params, (pageIndex-1)*pageSize, pageSize)
|
||||
itemSql += " order by s.connected_time desc LIMIT ?, ?"
|
||||
|
||||
db.Raw(countSql, params...).Scan(&total)
|
||||
|
||||
err = db.Raw(itemSql, params...).Scan(&results).Error
|
||||
|
||||
if results == nil {
|
||||
results = make([]SessionVo, 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func FindSessionByStatus(status string) (o []Session, err error) {
|
||||
err = config.DB.Where("status = ?", status).Find(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func CreateNewSession(o *Session) (err error) {
|
||||
err = config.DB.Create(o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func FindSessionById(id string) (o Session, err error) {
|
||||
err = config.DB.Where("id = ?", id).First(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func FindSessionByConnectionId(connectionId string) (o Session, err error) {
|
||||
err = config.DB.Where("connection_id = ?", connectionId).First(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func UpdateSessionById(o *Session, id string) {
|
||||
o.ID = id
|
||||
config.DB.Updates(o)
|
||||
}
|
||||
|
||||
func DeleteSessionById(id string) {
|
||||
config.DB.Where("id = ?", id).Delete(&Session{})
|
||||
}
|
||||
|
||||
func DeleteSessionByStatus(status string) {
|
||||
config.DB.Where("status = ?", status).Delete(&Session{})
|
||||
}
|
||||
|
||||
func CountOnlineSession() (total int64, err error) {
|
||||
err = config.DB.Where("status = ?", Connected).Find(&Session{}).Count(&total).Error
|
||||
return
|
||||
}
|
79
pkg/model/user.go
Normal file
79
pkg/model/user.go
Normal file
@ -0,0 +1,79 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"next-terminal/pkg/config"
|
||||
"next-terminal/pkg/utils"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string `gorm:"primary_key" json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
Online bool `json:"online"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Created utils.JsonTime `json:"created"`
|
||||
}
|
||||
|
||||
func (r *User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
func (r *User) IsEmpty() bool {
|
||||
return reflect.DeepEqual(r, User{})
|
||||
}
|
||||
|
||||
func FindAllUser() (o []User) {
|
||||
if config.DB.Find(&o).Error != nil {
|
||||
return nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func FindPageUser(pageIndex, pageSize int, username, nickname string) (o []User, total int64, err error) {
|
||||
|
||||
db := config.DB
|
||||
if len(username) > 0 {
|
||||
db = db.Where("username like ?", "%"+username+"%")
|
||||
}
|
||||
|
||||
if len(nickname) > 0 {
|
||||
db = db.Where("nickname like ?", "%"+nickname+"%")
|
||||
}
|
||||
|
||||
err = db.Order("created desc").Find(&o).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Count(&total).Error
|
||||
if o == nil {
|
||||
o = make([]User, 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CreateNewUser(o *User) (err error) {
|
||||
err = config.DB.Create(o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func FindUserById(id string) (o User, err error) {
|
||||
err = config.DB.Where("id = ?", id).First(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func FindUserByUsername(username string) (o User, err error) {
|
||||
err = config.DB.Where("username = ?", username).First(&o).Error
|
||||
return
|
||||
}
|
||||
|
||||
func UpdateUserById(o *User, id string) {
|
||||
o.ID = id
|
||||
config.DB.Updates(o)
|
||||
}
|
||||
|
||||
func DeleteUserById(id string) {
|
||||
config.DB.Where("id = ?", id).Delete(&User{})
|
||||
}
|
||||
|
||||
func CountUser() (total int64, err error) {
|
||||
err = config.DB.Find(&User{}).Count(&total).Error
|
||||
return
|
||||
}
|
81
pkg/utils/utils.go
Normal file
81
pkg/utils/utils.go
Normal file
@ -0,0 +1,81 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"github.com/gofrs/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JsonTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func NewJsonTime(t time.Time) JsonTime {
|
||||
return JsonTime{
|
||||
Time: t,
|
||||
}
|
||||
}
|
||||
|
||||
func NowJsonTime() JsonTime {
|
||||
return JsonTime{
|
||||
Time: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (t JsonTime) MarshalJSON() ([]byte, error) {
|
||||
var stamp = fmt.Sprintf("\"%s\"", t.Format("2006-01-02 15:04:05"))
|
||||
return []byte(stamp), nil
|
||||
}
|
||||
|
||||
func (t JsonTime) Value() (driver.Value, error) {
|
||||
var zeroTime time.Time
|
||||
if t.Time.UnixNano() == zeroTime.UnixNano() {
|
||||
return nil, nil
|
||||
}
|
||||
return t.Time, nil
|
||||
}
|
||||
|
||||
func (t *JsonTime) Scan(v interface{}) error {
|
||||
value, ok := v.(time.Time)
|
||||
if ok {
|
||||
*t = JsonTime{Time: value}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("can not convert %v to timestamp", v)
|
||||
}
|
||||
|
||||
type Bcrypt struct {
|
||||
cost int
|
||||
}
|
||||
|
||||
func (b *Bcrypt) Encode(password []byte) ([]byte, error) {
|
||||
return bcrypt.GenerateFromPassword(password, b.cost)
|
||||
}
|
||||
|
||||
func (b *Bcrypt) Match(hashedPassword, password []byte) error {
|
||||
return bcrypt.CompareHashAndPassword(hashedPassword, password)
|
||||
}
|
||||
|
||||
var Encoder = Bcrypt{
|
||||
cost: bcrypt.DefaultCost,
|
||||
}
|
||||
|
||||
func UUID() string {
|
||||
v4, _ := uuid.NewV4()
|
||||
return v4.String()
|
||||
}
|
||||
|
||||
func Tcping(ip string, port int) bool {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
if conn, err = net.DialTimeout("tcp", ip+":"+strconv.Itoa(port), 2*time.Second); err != nil {
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
return true
|
||||
}
|
Reference in New Issue
Block a user