Initial commit

This commit is contained in:
dushixiang
2020-12-20 21:19:11 +08:00
commit e7f2773c77
77 changed files with 27866 additions and 0 deletions

52
pkg/api/account.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}