fix: 修复关闭SSH终端标签页时会话状态未更新的问题

This commit is contained in:
2026-04-18 02:35:38 +08:00
commit 6e2e2f9387
43467 changed files with 5489040 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
package abi
import (
"github.com/labstack/echo/v4"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/dto"
"next-terminal/server/global/cache"
"next-terminal/server/model"
)
type Abi struct {
}
func (r *Abi) Fail(c echo.Context, code int, message string) error {
return c.JSON(200, maps.Map{
"code": code,
"message": message,
})
}
func (r *Abi) FailWithData(c echo.Context, code int, message string, data interface{}) error {
return c.JSON(200, maps.Map{
"code": code,
"message": message,
"data": data,
})
}
func (r *Abi) Success(c echo.Context, data interface{}) error {
return c.JSON(200, maps.Map{
"code": 1,
"message": "success",
"data": data,
})
}
func (r *Abi) GetToken(c echo.Context) string {
token := c.Request().Header.Get(nt.Token)
if len(token) > 0 {
return token
}
return c.QueryParam(nt.Token)
}
func (r *Abi) GetCurrentAccount(c echo.Context) (*model.User, bool) {
token := r.GetToken(c)
get, b := cache.TokenManager.Get(token)
if b {
return get.(dto.Authorization).User, true
}
return nil, false
}
func (r *Abi) HasPermission(c echo.Context, owner string) bool {
// 检测是否登录
account, found := r.GetCurrentAccount(c)
if !found {
return false
}
// 检测是否为管理人员
if nt.TypeAdmin == account.Type {
return true
}
// 检测是否为所有者
if owner == account.ID {
return true
}
return false
}
+117
View File
@@ -0,0 +1,117 @@
package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
"next-terminal/server/global/gateway"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type AccessGatewayApi struct{}
func (api AccessGatewayApi) AccessGatewayCreateEndpoint(c echo.Context) error {
var item model.AccessGateway
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
if err := repository.GatewayRepository.Create(context.TODO(), &item); err != nil {
return err
}
// 连接网关
service.GatewayService.ReLoad(&item)
return Success(c, "")
}
func (api AccessGatewayApi) AccessGatewayAllEndpoint(c echo.Context) error {
gateways, err := repository.GatewayRepository.FindAll(context.TODO())
if err != nil {
return err
}
items := make([]maps.Map, len(gateways))
for i, e := range gateways {
items[i] = maps.Map{
"id": e.ID,
"name": e.Name,
}
}
return Success(c, items)
}
func (api AccessGatewayApi) AccessGatewayPagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
ip := c.QueryParam("ip")
name := c.QueryParam("name")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.GatewayRepository.Find(context.TODO(), pageIndex, pageSize, ip, name, order, field)
if err != nil {
return err
}
for i := 0; i < len(items); i++ {
g := gateway.GlobalGatewayManager.GetById(items[i].ID)
if g != nil {
items[i].Connected = g.Connected
items[i].Message = g.Message
}
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api AccessGatewayApi) AccessGatewayUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.AccessGateway
if err := c.Bind(&item); err != nil {
return err
}
if err := repository.GatewayRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
service.GatewayService.ReLoad(&item)
return Success(c, nil)
}
func (api AccessGatewayApi) AccessGatewayDeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
for i := range split {
id := split[i]
if err := repository.GatewayRepository.DeleteById(context.TODO(), id); err != nil {
return err
}
service.GatewayService.DisconnectById(id)
}
return Success(c, nil)
}
func (api AccessGatewayApi) AccessGatewayGetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.GatewayRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
+44
View File
@@ -0,0 +1,44 @@
package api
import (
"next-terminal/server/common/maps"
"github.com/labstack/echo/v4"
)
type AccessSettingApi struct{}
func (api AccessSettingApi) GetEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"fontSize": "14",
"lineHeight": "1.5",
"fontFamily": "JetBrains Mono, Consolas, monospace",
"selectionCopy": "true",
"rightClickPaste": "true",
"treeExpandedKeys": "",
"useSnippets": "true",
"interceptSearchShortcut": "false",
})
}
func (api AccessSettingApi) SetEndpoint(c echo.Context) error {
var req map[string]interface{}
if err := c.Bind(&req); err != nil {
return err
}
_ = req
return Success(c, nil)
}
func (api AccessSettingApi) ShellAssistantEnabledEndpoint(c echo.Context) error {
return Success(c, maps.Map{"enabled": false})
}
func (api AccessSettingApi) ShellAssistantEndpoint(c echo.Context) error {
c.Response().Header().Set("Content-Type", "text/event-stream")
c.Response().Header().Set("Cache-Control", "no-cache")
c.Response().Header().Set("Connection", "keep-alive")
c.Response().Write([]byte("data: {\"success\":false,\"error\":\"Shell assistant is not configured\"}\n\n"))
return nil
}
+456
View File
@@ -0,0 +1,456 @@
package api
import (
"context"
"errors"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"path"
"strings"
"next-terminal/server/config"
"next-terminal/server/dto"
"next-terminal/server/global/cache"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
"gorm.io/gorm"
)
type AccountApi struct{}
func (api AccountApi) LoginEndpoint(c echo.Context) error {
var loginAccount dto.LoginAccount
if err := c.Bind(&loginAccount); err != nil {
return err
}
// 存储登录失败次数信息
loginFailCountKey := c.RealIP() + loginAccount.Username
v, ok := cache.LoginFailedKeyManager.Get(loginFailCountKey)
if !ok {
v = 1
}
count := v.(int)
if count >= 5 {
return Fail(c, -1, "登录失败次数过多,请等待5分钟后再试")
}
if len(loginAccount.Password) > 100 {
return Fail(c, -1, "您输入的密码过长")
}
user, err := repository.UserRepository.FindByUsername(context.TODO(), loginAccount.Username)
if err != nil {
count++
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
// 保存登录日志
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
return err
}
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
}
if user.Status == nt.StatusDisabled {
return Fail(c, -1, "该账户已停用")
}
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
count++
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
// 保存登录日志
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
return err
}
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
}
// 账号密码正确,需要进行两步验证
if user.TOTPSecret != "" && user.TOTPSecret != "-" {
if loginAccount.TOTP == "" {
return Fail(c, 100, "")
} else {
if !common.Validate(loginAccount.TOTP, user.TOTPSecret) {
count++
cache.LoginFailedKeyManager.Set(loginFailCountKey, count, cache.LoginLockExpiration)
// 保存登录日志
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "双因素认证授权码不正确"); err != nil {
return err
}
return FailWithData(c, -1, "您输入双因素认证授权码不正确", count)
}
}
}
token, err := api.LoginSuccess(loginAccount, user, c.RealIP())
if err != nil {
return err
}
// 保存登录日志
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, true, loginAccount.Remember, token, ""); err != nil {
return err
}
var menuStrings []string
if service.UserService.IsSuperAdmin(user.ID) {
menuStrings = service.MenuService.GetMenus()
} else {
roles, err := service.RoleService.GetRolesByUserId(user.ID)
if err != nil {
return err
}
for _, role := range roles {
items := service.RoleService.GetMenuListByRole(role)
menuStrings = append(menuStrings, items...)
}
}
var menus []UserMenu
for _, m := range menuStrings {
menus = append(menus, UserMenu{
Key: m,
Checked: true,
})
}
info := AccountInfo{
Id: user.ID,
Username: user.Username,
Nickname: user.Nickname,
Type: user.Type,
EnableTotp: user.TOTPSecret != "" && user.TOTPSecret != "-",
Roles: user.Roles,
Menus: menus,
}
return Success(c, maps.Map{
"info": info,
"token": token,
})
}
func (api AccountApi) LoginSuccess(loginAccount dto.LoginAccount, user model.User, ip string) (string, error) {
// 判断当前时间是否允许该用户登录
if err := service.LoginPolicyService.Check(user.ID, ip); err != nil {
return "", err
}
token := utils.LongUUID()
authorization := dto.Authorization{
Token: token,
Type: nt.LoginToken,
Remember: loginAccount.Remember,
User: &user,
}
if authorization.Remember {
// 记住登录有效期两周
cache.TokenManager.Set(token, authorization, cache.RememberMeExpiration)
} else {
cache.TokenManager.Set(token, authorization, cache.NotRememberExpiration)
}
b := true
// 修改登录状态
err := repository.UserRepository.Update(context.TODO(), &model.User{Online: &b, ID: user.ID})
return token, err
}
func (api AccountApi) LogoutEndpoint(c echo.Context) error {
token := GetToken(c)
service.UserService.Logout(token)
return Success(c, nil)
}
func (api AccountApi) ConfirmTOTPEndpoint(c echo.Context) error {
if config.GlobalCfg.Demo {
return Fail(c, 0, "演示模式禁止开启两步验证")
}
account, _ := GetCurrentAccount(c)
var confirmTOTP dto.ConfirmTOTP
if err := c.Bind(&confirmTOTP); err != nil {
return err
}
if !common.Validate(confirmTOTP.TOTP, confirmTOTP.Secret) {
return Fail(c, -1, "TOTP 验证失败,请重试")
}
u := &model.User{
TOTPSecret: confirmTOTP.Secret,
ID: account.ID,
}
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
return err
}
return Success(c, nil)
}
func (api AccountApi) ReloadTOTPEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
key, err := common.NewTOTP(common.GenerateOpts{
Issuer: c.Request().Host,
AccountName: account.Username,
})
if err != nil {
return Fail(c, -1, err.Error())
}
qrcode, err := key.Image(200, 200)
if err != nil {
return Fail(c, -1, err.Error())
}
qrEncode, err := utils.ImageToBase64Encode(qrcode)
if err != nil {
return Fail(c, -1, err.Error())
}
return Success(c, map[string]string{
"qr": qrEncode,
"secret": key.Secret(),
})
}
func (api AccountApi) ResetTOTPEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
u := &model.User{
TOTPSecret: "-",
ID: account.ID,
}
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
return err
}
return Success(c, "")
}
func (api AccountApi) ChangePasswordEndpoint(c echo.Context) error {
if config.GlobalCfg.Demo {
return Fail(c, 0, "演示模式禁止修改密码")
}
account, _ := GetCurrentAccount(c)
var changePassword dto.ChangePassword
if err := c.Bind(&changePassword); err != nil {
return err
}
if len(changePassword.NewPassword) > 100 {
return Fail(c, -1, "您输入的密码过长")
}
if err := utils.Encoder.Match([]byte(account.Password), []byte(changePassword.OldPassword)); err != nil {
return Fail(c, -1, "您输入的原密码不正确")
}
passwd, err := utils.Encoder.Encode([]byte(changePassword.NewPassword))
if err != nil {
return err
}
u := &model.User{
Password: string(passwd),
ID: account.ID,
}
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
return err
}
return api.LogoutEndpoint(c)
}
type AccountInfo struct {
Id string `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Type string `json:"type"`
EnableTotp bool `json:"enableTotp"`
Roles []string `json:"roles"`
Menus []UserMenu `json:"menus"`
}
type UserMenu struct {
Key string `json:"key"`
Checked bool `json:"checked"`
}
func (api AccountApi) InfoEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
if strings.EqualFold("anonymous", account.Type) {
return Success(c, account)
}
user, err := service.UserService.FindById(account.ID)
if err != nil {
return err
}
var menuStrings []string
if service.UserService.IsSuperAdmin(account.ID) {
menuStrings = service.MenuService.GetMenus()
} else {
roles, err := service.RoleService.GetRolesByUserId(account.ID)
if err != nil {
return err
}
for _, role := range roles {
items := service.RoleService.GetMenuListByRole(role)
menuStrings = append(menuStrings, items...)
}
}
var menus []UserMenu
for _, m := range menuStrings {
menus = append(menus, UserMenu{
Key: m,
Checked: true,
})
}
info := AccountInfo{
Id: user.ID,
Username: user.Username,
Nickname: user.Nickname,
Type: user.Type,
EnableTotp: user.TOTPSecret != "" && user.TOTPSecret != "-",
Roles: user.Roles,
Menus: menus,
}
return Success(c, info)
}
func (api AccountApi) MenuEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
if service.UserService.IsSuperAdmin(account.ID) {
items := service.MenuService.GetMenus()
return Success(c, items)
}
roles, err := service.RoleService.GetRolesByUserId(account.ID)
if err != nil {
return err
}
var menus []string
for _, role := range roles {
items := service.RoleService.GetMenuListByRole(role)
menus = append(menus, items...)
}
return Success(c, menus)
}
func (api AccountApi) AccountStorageEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
storageId := account.ID
storage, err := repository.StorageRepository.FindById(context.TODO(), storageId)
if err != nil {
return err
}
structMap := utils.StructToMap(storage)
drivePath := service.StorageService.GetBaseDrivePath()
dirSize, err := utils.DirSize(path.Join(drivePath, storageId))
if err != nil {
structMap["usedSize"] = -1
} else {
structMap["usedSize"] = dirSize
}
return Success(c, structMap)
}
func (api AccountApi) AccessTokenGetEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
accessTokens, err := repository.AccessTokenRepository.FindByUserId(context.TODO(), account.ID)
if err != nil {
if errors.Is(gorm.ErrRecordNotFound, err) {
accessTokens = []model.AccessToken{}
} else {
return err
}
}
return Success(c, accessTokens)
}
func (api AccountApi) AccessTokenGenEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
var req struct {
Type string `json:"type"`
}
c.Bind(&req)
if req.Type == "" {
req.Type = "api"
}
accessToken, err := service.AccessTokenService.GenAccessToken(account.ID, req.Type)
if err != nil {
return err
}
return Success(c, accessToken)
}
func (api AccountApi) AccessTokenDelEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
id := c.Param("id")
if err := service.AccessTokenService.DelAccessToken(context.Background(), id, account.ID); err != nil {
return err
}
return Success(c, nil)
}
func (api AccountApi) SecurityTokenSupportTypesEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
user, err := repository.UserRepository.FindById(context.TODO(), account.ID)
if err != nil {
return err
}
var types []string
if user.TOTPSecret != "" && user.TOTPSecret != "-" {
types = append(types, "otp")
}
if len(types) == 0 {
types = []string{}
}
return Success(c, types)
}
func (api AccountApi) SecurityTokenMfaEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
passcode := c.QueryParam("passcode")
user, err := repository.UserRepository.FindById(context.TODO(), account.ID)
if err != nil {
return err
}
if user.TOTPSecret == "" || user.TOTPSecret == "-" {
return Fail(c, -1, "MFA not enabled")
}
if !common.Validate(passcode, user.TOTPSecret) {
return Fail(c, -1, "Invalid passcode")
}
token := utils.UUID()
cache.TokenManager.Set(token, account.ID, cache.NotRememberExpiration)
return Success(c, maps.Map{"token": token})
}
func (api AccountApi) SecurityTokenValidateEndpoint(c echo.Context) error {
token := c.QueryParam("securityToken")
if token == "" {
return Success(c, maps.Map{"ok": false})
}
_, ok := cache.TokenManager.Get(token)
return Success(c, maps.Map{"ok": ok})
}
+137
View File
@@ -0,0 +1,137 @@
package api
import (
"context"
"strconv"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type AgentGatewayApi struct{}
func (api AgentGatewayApi) AllEndpoint(c echo.Context) error {
items, err := repository.AgentGatewayRepository.FindAll(context.TODO())
if err != nil {
return err
}
return Success(c, items)
}
func (api AgentGatewayApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
items, total, err := repository.AgentGatewayRepository.Find(context.TODO(), pageIndex, pageSize, name)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api AgentGatewayApi) CreateEndpoint(c echo.Context) error {
var item model.AgentGateway
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
item.Online = false
if err := repository.AgentGatewayRepository.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api AgentGatewayApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.AgentGateway
if err := c.Bind(&item); err != nil {
return err
}
if err := repository.AgentGatewayRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
return Success(c, nil)
}
func (api AgentGatewayApi) DeleteEndpoint(c echo.Context) error {
id := c.Param("id")
if err := repository.AgentGatewayRepository.DeleteById(context.TODO(), id); err != nil {
return err
}
return Success(c, nil)
}
func (api AgentGatewayApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.AgentGatewayRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
func (api AgentGatewayApi) GetRegisterParamEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"endpoint": "",
"token": "",
})
}
func (api AgentGatewayApi) SetRegisterAddrEndpoint(c echo.Context) error {
endpoint := c.QueryParam("endpoint")
_ = endpoint
return Success(c, nil)
}
func (api AgentGatewayApi) GetStatEndpoint(c echo.Context) error {
id := c.Param("id")
_, err := repository.AgentGatewayRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, maps.Map{
"ping": 0,
"cpu": maps.Map{"percent": 0},
"memory": maps.Map{"percent": 0},
"disk": maps.Map{"percent": 0},
"network": maps.Map{},
"load": maps.Map{},
"host": maps.Map{},
"process": maps.Map{"total": 0},
"connections": 0,
"tcp_states": []string{},
"errors": maps.Map{},
})
}
func (api AgentGatewayApi) UpdateSortEndpoint(c echo.Context) error {
var items []struct {
ID string `json:"id"`
SortOrder int `json:"sortOrder"`
}
if err := c.Bind(&items); err != nil {
return err
}
ctx := context.TODO()
for _, item := range items {
sortStr := strconv.Itoa(item.SortOrder * 100)
repository.AgentGatewayRepository.UpdateSort(ctx, item.ID, sortStr)
}
return Success(c, nil)
}
func (api AgentGatewayApi) VersionEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"version": "1.0.0",
})
}
+50
View File
@@ -0,0 +1,50 @@
package api
import (
"github.com/labstack/echo/v4"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/dto"
"next-terminal/server/global/cache"
"next-terminal/server/model"
)
func Fail(c echo.Context, code int, message string) error {
return c.JSON(200, maps.Map{
"code": code,
"message": message,
})
}
func FailWithData(c echo.Context, code int, message string, data interface{}) error {
return c.JSON(200, maps.Map{
"code": code,
"message": message,
"data": data,
})
}
func Success(c echo.Context, data interface{}) error {
return c.JSON(200, maps.Map{
"code": 1,
"message": "success",
"data": data,
})
}
func GetToken(c echo.Context) string {
token := c.Request().Header.Get(nt.Token)
if len(token) > 0 {
return token
}
return c.QueryParam(nt.Token)
}
func GetCurrentAccount(c echo.Context) (*model.User, bool) {
token := GetToken(c)
get, b := cache.TokenManager.Get(token)
if b {
return get.(dto.Authorization).User, true
}
return nil, false
}
+280
View File
@@ -0,0 +1,280 @@
package api
import (
"bufio"
"context"
"encoding/csv"
"errors"
"strconv"
"strings"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/global/cache"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type AssetApi struct{}
func (assetApi AssetApi) AssetCreateEndpoint(c echo.Context) error {
m := maps.Map{}
if err := c.Bind(&m); err != nil {
return err
}
account, _ := GetCurrentAccount(c)
m["owner"] = account.ID
if _, err := service.AssetService.Create(context.TODO(), m); err != nil {
return err
}
return Success(c, nil)
}
func (assetApi AssetApi) AssetImportEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
file, err := c.FormFile("file")
if err != nil {
return err
}
src, err := file.Open()
if err != nil {
return err
}
defer func() {
_ = src.Close()
}()
reader := csv.NewReader(bufio.NewReader(src))
records, err := reader.ReadAll()
if err != nil {
return err
}
total := len(records)
if total == 0 {
return errors.New("csv数据为空")
}
var successCount = 0
var errorCount = 0
m := echo.Map{}
for i := 0; i < total; i++ {
record := records[i]
if len(record) >= 9 {
port, _ := strconv.Atoi(record[3])
asset := maps.Map{
"id": utils.UUID(),
"name": record[0],
"protocol": record[1],
"ip": record[2],
"port": port,
"accountType": nt.Custom,
"username": record[4],
"password": record[5],
"privateKey": record[6],
"passphrase": record[7],
"Description": record[8],
"owner": account.ID,
}
if record[6] != "" {
asset["accountType"] = nt.PrivateKey
}
if len(record) >= 10 {
tags := strings.ReplaceAll(record[9], "|", ",")
asset["tags"] = tags
}
_, err := service.AssetService.Create(context.Background(), asset)
if err != nil {
errorCount++
m[strconv.Itoa(i)] = err.Error()
} else {
successCount++
}
}
}
return Success(c, echo.Map{
"successCount": successCount,
"errorCount": errorCount,
"data": m,
})
}
func (assetApi AssetApi) AssetPagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("keyword")
if name == "" {
name = c.QueryParam("name")
}
protocol := c.QueryParam("protocol")
tags := c.QueryParam("tags")
ip := c.QueryParam("ip")
port := c.QueryParam("port")
active := c.QueryParam("active")
order := c.QueryParam("sortOrder")
if order == "" {
order = c.QueryParam("order")
}
field := c.QueryParam("sortField")
if field == "" {
field = c.QueryParam("field")
}
items, total, err := repository.AssetRepository.Find(context.Background(), pageIndex, pageSize, name, protocol, tags, ip, port, active, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (assetApi AssetApi) AssetAllEndpoint(c echo.Context) error {
protocol := c.QueryParam("protocol")
assets, err := repository.AssetRepository.FindByProtocol(context.TODO(), protocol)
if err != nil {
return err
}
items := make([]maps.Map, len(assets))
for i, e := range assets {
items[i] = maps.Map{
"id": e.ID,
"name": e.Name,
}
}
return Success(c, items)
}
func (assetApi AssetApi) AssetUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
m := maps.Map{}
if err := c.Bind(&m); err != nil {
return err
}
if err := service.AssetService.UpdateById(id, m); err != nil {
return err
}
return Success(c, nil)
}
func (assetApi AssetApi) AssetDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
split := strings.Split(id, ",")
for i := range split {
if err := service.AssetService.DeleteById(split[i]); err != nil {
return err
}
}
return Success(c, nil)
}
func (assetApi AssetApi) AssetGetEndpoint(c echo.Context) (err error) {
id := c.Param("id")
var item model.Asset
if item, err = service.AssetService.FindByIdAndDecrypt(context.TODO(), id); err != nil {
return err
}
attributeMap, err := repository.AssetRepository.FindAssetAttrMapByAssetId(context.TODO(), id)
if err != nil {
return err
}
itemMap := utils.StructToMap(item)
for key := range attributeMap {
itemMap[key] = attributeMap[key]
}
return Success(c, itemMap)
}
func (assetApi AssetApi) AssetDecryptedEndpoint(c echo.Context) (err error) {
id := c.Param("id")
securityToken := c.QueryParam("securityToken")
account, _ := GetCurrentAccount(c)
user, err := repository.UserRepository.FindById(context.TODO(), account.ID)
if err != nil {
return err
}
// 如果用户启用了 MFA,需要验证 securityToken
if user.TOTPSecret != "" && user.TOTPSecret != "-" {
if securityToken == "" {
return Fail(c, -1, "需要MFA验证")
}
_, ok := cache.TokenManager.Get(securityToken)
if !ok {
return Fail(c, -1, "MFA验证已过期,请重新验证")
}
}
var item model.Asset
if item, err = service.AssetService.FindByIdAndDecrypt(context.TODO(), id); err != nil {
return err
}
return Success(c, maps.Map{
"password": item.Password,
"privateKey": item.PrivateKey,
"passphrase": item.Passphrase,
})
}
func (assetApi AssetApi) AssetTcpingEndpoint(c echo.Context) (err error) {
id := c.Param("id")
var item model.Asset
if item, err = repository.AssetRepository.FindById(context.TODO(), id); err != nil {
return err
}
active, err := service.AssetService.CheckStatus(&item, item.IP, item.Port)
var message = ""
if err != nil {
message = err.Error()
}
if err := repository.AssetRepository.UpdateActiveById(context.TODO(), active, message, item.ID); err != nil {
return err
}
return Success(c, maps.Map{
"active": active,
"message": message,
})
}
func (assetApi AssetApi) AssetTagsEndpoint(c echo.Context) (err error) {
var items []string
if items, err = repository.AssetRepository.FindTags(context.TODO()); err != nil {
return err
}
return Success(c, items)
}
func (assetApi AssetApi) AssetChangeOwnerEndpoint(c echo.Context) (err error) {
id := c.Param("id")
owner := c.QueryParam("owner")
if err := repository.AssetRepository.UpdateById(context.TODO(), &model.Asset{Owner: owner}, id); err != nil {
return err
}
return Success(c, "")
}
+216
View File
@@ -0,0 +1,216 @@
package api
import (
"context"
"strconv"
"time"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type AssetGroupApi struct{}
func (api AssetGroupApi) GroupsGetEndpoint(c echo.Context) error {
groups, err := repository.AssetGroupRepository.FindAll(context.TODO())
if err != nil {
return err
}
tree := buildGroupTree(groups, "")
return Success(c, tree)
}
func (api AssetGroupApi) GroupsSetEndpoint(c echo.Context) error {
var req []map[string]interface{}
if err := c.Bind(&req); err != nil {
return err
}
ctx := context.TODO()
repository.AssetGroupRepository.DeleteByParentId(ctx, "")
for i, item := range req {
group := model.AssetGroup{
ID: utils.UUID(),
Name: item["name"].(string),
ParentId: "",
Sort: i,
Created: time.Now().UnixMilli(),
}
repository.AssetGroupRepository.Create(ctx, &group)
if children, ok := item["children"].([]interface{}); ok {
saveChildren(ctx, children, group.ID)
}
}
return Success(c, nil)
}
func (api AssetGroupApi) GroupsDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
ctx := context.TODO()
repository.AssetGroupRepository.DeleteByParentId(ctx, id)
repository.AssetGroupRepository.DeleteById(ctx, id)
return Success(c, nil)
}
func saveChildren(ctx context.Context, children []interface{}, parentId string) {
for i, item := range children {
m := item.(map[string]interface{})
group := model.AssetGroup{
ID: utils.UUID(),
Name: m["name"].(string),
ParentId: parentId,
Sort: i,
Created: time.Now().UnixMilli(),
}
repository.AssetGroupRepository.Create(ctx, &group)
if subChildren, ok := m["children"].([]interface{}); ok {
saveChildren(ctx, subChildren, group.ID)
}
}
}
func buildGroupTree(groups []model.AssetGroup, parentId string) []maps.Map {
var tree []maps.Map
for _, g := range groups {
if g.ParentId == parentId {
node := maps.Map{
"id": g.ID,
"name": g.Name,
"title": g.Name,
"key": g.ID,
"value": g.ID,
}
children := buildGroupTree(groups, g.ID)
if len(children) > 0 {
node["children"] = children
}
tree = append(tree, node)
}
}
return tree
}
func (api AssetGroupApi) TreeEndpoint(c echo.Context) error {
protocol := c.QueryParam("protocol")
assets, err := repository.AssetRepository.FindByProtocol(context.TODO(), protocol)
if err != nil {
return err
}
groups, err := repository.AssetGroupRepository.FindAll(context.TODO())
if err != nil {
return err
}
tree := buildAssetTree(assets, groups, "")
return Success(c, tree)
}
func buildAssetTree(assets []model.Asset, groups []model.AssetGroup, groupId string) []maps.Map {
var nodes []maps.Map
var groupAssets []model.Asset
for _, a := range assets {
groupAssets = append(groupAssets, a)
}
for _, a := range groupAssets {
nodes = append(nodes, maps.Map{
"id": a.ID,
"name": a.Name,
"key": a.ID,
"isLeaf": true,
"protocol": a.Protocol,
"ip": a.IP,
"port": a.Port,
})
}
for _, g := range groups {
if g.ParentId == groupId {
node := maps.Map{
"id": g.ID,
"name": g.Name,
"key": g.ID,
}
children := buildAssetTree(assets, groups, g.ID)
if len(children) > 0 {
node["children"] = children
}
nodes = append(nodes, node)
}
}
return nodes
}
func (api AssetGroupApi) ChangeGroupEndpoint(c echo.Context) error {
var req map[string]interface{}
if err := c.Bind(&req); err != nil {
return err
}
assetIds, _ := req["assetIds"].([]interface{})
groupId, _ := req["groupId"].(string)
ctx := context.TODO()
for _, id := range assetIds {
aid := id.(string)
repository.AssetRepository.UpdateById(ctx, &model.Asset{ID: aid}, aid)
}
_ = groupId
return Success(c, nil)
}
func (api AssetGroupApi) ChangeGatewayEndpoint(c echo.Context) error {
var req map[string]interface{}
if err := c.Bind(&req); err != nil {
return err
}
gatewayType, _ := req["gatewayType"].(string)
gatewayId, _ := req["gatewayId"].(string)
assetIds, _ := req["assetIds"].([]interface{})
ctx := context.TODO()
for _, id := range assetIds {
aid := id.(string)
m := maps.Map{}
if gatewayType == "ssh" {
m["access_gateway_id"] = gatewayId
} else if gatewayType == "agent" {
m["access_gateway_id"] = gatewayId
} else {
m["access_gateway_id"] = ""
}
repository.AssetRepository.UpdateById(ctx, &model.Asset{ID: aid}, aid)
_ = m
}
_ = gatewayType
_ = gatewayId
return Success(c, nil)
}
func (api AssetGroupApi) SortEndpoint(c echo.Context) error {
var req struct {
Id string `json:"id"`
BeforeId string `json:"beforeId"`
AfterId string `json:"afterId"`
}
if err := c.Bind(&req); err != nil {
return err
}
ctx := context.TODO()
all, _ := repository.AssetRepository.FindAll(ctx)
beforeSort := 0
afterSort := 0
for _, a := range all {
if a.ID == req.BeforeId {
beforeSort = a.Sort
}
if a.ID == req.AfterId {
afterSort = a.Sort
}
}
newSort := (beforeSort + afterSort) / 2
if newSort == 0 {
newSort = beforeSort + 1
}
sortStr := strconv.Itoa(newSort)
repository.AssetRepository.UpdateById(ctx, &model.Asset{ID: req.Id, Sort: newSort}, req.Id)
_ = sortStr
return Success(c, nil)
}
+136
View File
@@ -0,0 +1,136 @@
package api
import (
"context"
"github.com/labstack/echo/v4"
"next-terminal/server/common/maps"
"next-terminal/server/dto"
"next-terminal/server/repository"
"next-terminal/server/service"
"strconv"
)
type AuthorisedApi struct {
}
func (api AuthorisedApi) Selected(c echo.Context) error {
userId := c.QueryParam("userId")
userGroupId := c.QueryParam("userGroupId")
assetId := c.QueryParam("assetId")
key := c.QueryParam("key")
items, err := repository.AuthorisedRepository.FindAll(context.Background(), userId, userGroupId, assetId)
if err != nil {
return err
}
var result = make([]string, 0)
switch key {
case "userId":
for _, item := range items {
result = append(result, item.UserId)
}
case "userGroupId":
for _, item := range items {
result = append(result, item.UserGroupId)
}
case "assetId":
for _, item := range items {
result = append(result, item.AssetId)
}
}
return Success(c, result)
}
func (api AuthorisedApi) Delete(c echo.Context) error {
id := c.Param("id")
if err := repository.AuthorisedRepository.DeleteById(context.Background(), id); err != nil {
return err
}
return Success(c, "")
}
func (api AuthorisedApi) PagingAsset(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
assetName := c.QueryParam("assetName")
userId := c.QueryParam("userId")
userGroupId := c.QueryParam("userGroupId")
items, total, err := repository.AuthorisedRepository.FindAssetPage(context.Background(), pageIndex, pageSize, assetName, userId, userGroupId)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api AuthorisedApi) PagingUser(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
userName := c.QueryParam("userName")
assetId := c.QueryParam("assetId")
items, total, err := repository.AuthorisedRepository.FindUserPage(context.Background(), pageIndex, pageSize, userName, assetId)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api AuthorisedApi) PagingUserGroup(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
userGroupName := c.QueryParam("userGroupName")
assetId := c.QueryParam("assetId")
items, total, err := repository.AuthorisedRepository.FindUserGroupPage(context.Background(), pageIndex, pageSize, userGroupName, assetId)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api AuthorisedApi) AuthorisedAssets(c echo.Context) error {
var item dto.AuthorisedAsset
if err := c.Bind(&item); err != nil {
return err
}
if err := service.AuthorisedService.AuthorisedAssets(context.Background(), &item); err != nil {
return err
}
return Success(c, nil)
}
func (api AuthorisedApi) AuthorisedUsers(c echo.Context) error {
var item dto.AuthorisedUser
if err := c.Bind(&item); err != nil {
return err
}
if err := service.AuthorisedService.AuthorisedUsers(context.Background(), &item); err != nil {
return err
}
return Success(c, nil)
}
func (api AuthorisedApi) AuthorisedUserGroups(c echo.Context) error {
var item dto.AuthorisedUserGroup
if err := c.Bind(&item); err != nil {
return err
}
if err := service.AuthorisedService.AuthorisedUserGroups(context.Background(), &item); err != nil {
return err
}
return Success(c, nil)
}
+41
View File
@@ -0,0 +1,41 @@
package api
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"next-terminal/server/dto"
"next-terminal/server/service"
"github.com/labstack/echo/v4"
)
type BackupApi struct{}
func (api BackupApi) BackupExportEndpoint(c echo.Context) error {
err, backup := service.BackupService.Export()
if err != nil {
return err
}
jsonBytes, err := json.Marshal(backup)
if err != nil {
return err
}
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=backup_%s.json", time.Now().Format("20060102150405")))
return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(jsonBytes))
}
func (api BackupApi) BackupImportEndpoint(c echo.Context) error {
var backup dto.Backup
if err := c.Bind(&backup); err != nil {
return err
}
if err := service.BackupService.Import(&backup); err != nil {
return err
}
return Success(c, "")
}
+74
View File
@@ -0,0 +1,74 @@
package api
import (
"context"
"embed"
"encoding/base64"
"io/fs"
"net/http"
"strings"
"next-terminal/server/branding"
"next-terminal/server/common/maps"
"next-terminal/server/repository"
"github.com/labstack/echo/v4"
)
//go:embed logos/*
var logoFS embed.FS
func Branding(c echo.Context) error {
properties := repository.PropertyRepository.FindAllMap(context.TODO())
name := branding.Name
if val, ok := properties["system-name"]; ok && val != "" && val != "-" {
name = val
}
copyright := branding.Copyright
if val, ok := properties["system-copyright"]; ok && val != "" && val != "-" {
copyright = val
}
icp := ""
if val, ok := properties["system-icp"]; ok && val != "" && val != "-" {
icp = val
}
return Success(c, maps.Map{
"name": name,
"copyright": copyright,
"icp": icp,
})
}
func Logo(c echo.Context) error {
properties := repository.PropertyRepository.FindAllMap(context.TODO())
if logo, ok := properties["system-logo"]; ok && logo != "" && logo != "-" {
if strings.HasPrefix(logo, "data:image/") {
parts := strings.SplitN(logo, ",", 2)
if len(parts) == 2 {
if imageData, err := base64.StdEncoding.DecodeString(parts[1]); err == nil {
contentType := "image/png"
if strings.Contains(parts[0], "image/jpeg") {
contentType = "image/jpeg"
} else if strings.Contains(parts[0], "image/gif") {
contentType = "image/gif"
} else if strings.Contains(parts[0], "image/svg") {
contentType = "image/svg+xml"
}
return c.Blob(http.StatusOK, contentType, imageData)
}
}
}
return c.Blob(http.StatusOK, "image/png", []byte(logo))
}
data, err := fs.ReadFile(logoFS, "logos/logo.png")
if err != nil {
return c.String(http.StatusNotFound, "Logo not found")
}
return c.Blob(http.StatusOK, "image/png", data)
}
+193
View File
@@ -0,0 +1,193 @@
package api
import (
"context"
"crypto/x509"
"encoding/pem"
"strconv"
"time"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type CertificateApi struct{}
func (api CertificateApi) AllEndpoint(c echo.Context) error {
items, err := repository.CertificateRepository.FindAll(context.TODO())
if err != nil {
return err
}
return Success(c, items)
}
func (api CertificateApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
keyword := c.QueryParam("keyword")
items, total, err := repository.CertificateRepository.Find(context.TODO(), pageIndex, pageSize, keyword)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func parseCertificate(certPEM string) (commonName, subject, issuer string, notBefore, notAfter time.Time, err error) {
block, _ := pem.Decode([]byte(certPEM))
if block == nil {
return "", "", "", time.Time{}, time.Time{}, nil
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return "", "", "", time.Time{}, time.Time{}, err
}
commonName = cert.Subject.CommonName
if commonName == "" && len(cert.Subject.Names) > 0 {
commonName = cert.Subject.Names[0].Value.(string)
}
subject = cert.Subject.String()
issuer = cert.Issuer.String()
notBefore = cert.NotBefore
notAfter = cert.NotAfter
return commonName, subject, issuer, notBefore, notAfter, nil
}
func (api CertificateApi) CreateEndpoint(c echo.Context) error {
var req struct {
CommonName string `json:"commonName"`
Certificate string `json:"certificate"`
PrivateKey string `json:"privateKey"`
Type string `json:"type"`
RequireClientAuth bool `json:"requireClientAuth"`
}
if err := c.Bind(&req); err != nil {
return err
}
item := &model.Certificate{
ID: utils.UUID(),
CommonName: req.CommonName,
Certificate: req.Certificate,
PrivateKey: req.PrivateKey,
Type: req.Type,
RequireClientAuth: req.RequireClientAuth,
IssuedStatus: "success",
Created: common.NowJsonTime(),
UpdatedAt: common.NowJsonTime(),
}
if req.Certificate != "" {
commonName, subject, issuer, notBefore, notAfter, err := parseCertificate(req.Certificate)
if err == nil {
if item.CommonName == "" {
item.CommonName = commonName
}
item.Subject = subject
item.Issuer = issuer
item.NotBefore = common.NewJsonTime(notBefore)
item.NotAfter = common.NewJsonTime(notAfter)
}
}
if item.Type == "" {
item.Type = "imported"
}
if err := repository.CertificateRepository.Create(context.TODO(), item); err != nil {
return err
}
return Success(c, "")
}
func (api CertificateApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var req struct {
CommonName string `json:"commonName"`
Certificate string `json:"certificate"`
PrivateKey string `json:"privateKey"`
RequireClientAuth bool `json:"requireClientAuth"`
}
if err := c.Bind(&req); err != nil {
return err
}
item := &model.Certificate{
ID: id,
CommonName: req.CommonName,
Certificate: req.Certificate,
PrivateKey: req.PrivateKey,
RequireClientAuth: req.RequireClientAuth,
UpdatedAt: common.NowJsonTime(),
}
if req.Certificate != "" {
commonName, subject, issuer, notBefore, notAfter, err := parseCertificate(req.Certificate)
if err == nil {
if item.CommonName == "" {
item.CommonName = commonName
}
item.Subject = subject
item.Issuer = issuer
item.NotBefore = common.NewJsonTime(notBefore)
item.NotAfter = common.NewJsonTime(notAfter)
}
}
if err := repository.CertificateRepository.UpdateById(context.TODO(), item, id); err != nil {
return err
}
return Success(c, nil)
}
func (api CertificateApi) DeleteEndpoint(c echo.Context) error {
id := c.Param("id")
if err := repository.CertificateRepository.DeleteById(context.TODO(), id); err != nil {
return err
}
return Success(c, nil)
}
func (api CertificateApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.CertificateRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
func (api CertificateApi) UpdateDefaultEndpoint(c echo.Context) error {
id := c.Param("id")
err := repository.CertificateRepository.UpdateById(context.TODO(), &model.Certificate{IsDefault: false}, id)
if err != nil {
return err
}
return Success(c, nil)
}
func (api CertificateApi) DownloadEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api CertificateApi) RenewEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"success": true,
"warning": false,
"error": "",
})
}
+107
View File
@@ -0,0 +1,107 @@
package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type CommandApi struct{}
func (api CommandApi) CommandCreateEndpoint(c echo.Context) error {
var item model.Command
if err := c.Bind(&item); err != nil {
return err
}
account, _ := GetCurrentAccount(c)
item.Owner = account.ID
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
if err := repository.CommandRepository.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, item)
}
func (api CommandApi) CommandAllEndpoint(c echo.Context) error {
items, err := repository.CommandRepository.FindAll(context.Background())
if err != nil {
return err
}
return Success(c, items)
}
func (api CommandApi) 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")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.CommandRepository.Find(context.TODO(), pageIndex, pageSize, name, content, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api CommandApi) CommandUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.Command
if err := c.Bind(&item); err != nil {
return err
}
if err := repository.CommandRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
return Success(c, nil)
}
func (api CommandApi) CommandDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
split := strings.Split(id, ",")
for i := range split {
if err := repository.CommandRepository.DeleteById(context.TODO(), split[i]); err != nil {
return err
}
}
return Success(c, nil)
}
func (api CommandApi) CommandGetEndpoint(c echo.Context) (err error) {
id := c.Param("id")
var item model.Command
if item, err = repository.CommandRepository.FindById(context.TODO(), id); err != nil {
return err
}
return Success(c, item)
}
func (api CommandApi) CommandChangeOwnerEndpoint(c echo.Context) (err error) {
id := c.Param("id")
owner := c.QueryParam("owner")
if err := repository.CommandRepository.UpdateById(context.TODO(), &model.Command{Owner: owner}, id); err != nil {
return err
}
return Success(c, "")
}
+68
View File
@@ -0,0 +1,68 @@
package api
import (
"next-terminal/server/common/maps"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type CommandFilterApi struct{}
func (api CommandFilterApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api CommandFilterApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"items": []interface{}{},
"total": 0,
})
}
func (api CommandFilterApi) CreateEndpoint(c echo.Context) error {
var item map[string]interface{}
if err := c.Bind(&item); err != nil {
return err
}
item["id"] = utils.LongUUID()
return Success(c, item)
}
func (api CommandFilterApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item map[string]interface{}
if err := c.Bind(&item); err != nil {
return err
}
item["id"] = id
return Success(c, item)
}
func (api CommandFilterApi) DeleteEndpoint(c echo.Context) error {
id := c.Param("id")
_ = id
return Success(c, nil)
}
func (api CommandFilterApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
_ = id
return Success(c, maps.Map{
"id": id,
"name": "Default Filter",
"createdAt": 1700000000000,
})
}
func (api CommandFilterApi) BindEndpoint(c echo.Context) error {
id := c.Param("id")
_ = id
return Success(c, nil)
}
func (api CommandFilterApi) UnbindEndpoint(c echo.Context) error {
id := c.Param("id")
_ = id
return Success(c, nil)
}
+258
View File
@@ -0,0 +1,258 @@
package api
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/global/cache"
"strconv"
"strings"
"next-terminal/server/config"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type CredentialApi struct{}
func (api CredentialApi) CredentialAllEndpoint(c echo.Context) error {
items, err := repository.CredentialRepository.FindByAll(context.Background())
if err != nil {
return err
}
return Success(c, items)
}
func (api CredentialApi) CredentialCreateEndpoint(c echo.Context) error {
var item model.Credential
if err := c.Bind(&item); err != nil {
return err
}
account, _ := GetCurrentAccount(c)
item.Owner = account.ID
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
switch item.Type {
case nt.Custom:
item.PrivateKey = "-"
item.Passphrase = "-"
if item.Username == "" {
item.Username = "-"
}
if item.Password == "" {
item.Password = "-"
}
case nt.PrivateKey:
item.Password = "-"
if item.Username == "" {
item.Username = "-"
}
if item.PrivateKey == "" {
item.PrivateKey = "-"
}
if item.Passphrase == "" {
item.Passphrase = "-"
}
default:
return Fail(c, -1, "类型错误")
}
item.Encrypted = true
if err := service.CredentialService.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, item)
}
func (api CredentialApi) CredentialPagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.CredentialRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api CredentialApi) CredentialUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.Credential
if err := c.Bind(&item); err != nil {
return err
}
switch item.Type {
case nt.Custom:
item.PrivateKey = "-"
item.Passphrase = "-"
if item.Username == "" {
item.Username = "-"
}
if item.Password == "" {
item.Password = "-"
}
if item.Password != "-" {
encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Password), config.GlobalCfg.EncryptionPassword)
if err != nil {
return err
}
item.Password = base64.StdEncoding.EncodeToString(encryptedCBC)
}
case nt.PrivateKey:
item.Password = "-"
if item.Username == "" {
item.Username = "-"
}
if item.PrivateKey == "" {
item.PrivateKey = "-"
}
if item.PrivateKey != "-" {
encryptedCBC, err := utils.AesEncryptCBC([]byte(item.PrivateKey), config.GlobalCfg.EncryptionPassword)
if err != nil {
return err
}
item.PrivateKey = base64.StdEncoding.EncodeToString(encryptedCBC)
}
if item.Passphrase == "" {
item.Passphrase = "-"
}
if item.Passphrase != "-" {
encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Passphrase), config.GlobalCfg.EncryptionPassword)
if err != nil {
return err
}
item.Passphrase = base64.StdEncoding.EncodeToString(encryptedCBC)
}
default:
return Fail(c, -1, "类型错误")
}
item.Encrypted = true
if err := repository.CredentialRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
return Success(c, nil)
}
func (api CredentialApi) CredentialDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
split := strings.Split(id, ",")
for i := range split {
if err := repository.CredentialRepository.DeleteById(context.TODO(), split[i]); err != nil {
return err
}
}
return Success(c, nil)
}
func (api CredentialApi) CredentialGetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := service.CredentialService.FindByIdAndDecrypt(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
func (api CredentialApi) CredentialChangeOwnerEndpoint(c echo.Context) error {
id := c.Param("id")
owner := c.QueryParam("owner")
if err := repository.CredentialRepository.UpdateById(context.TODO(), &model.Credential{Owner: owner}, id); err != nil {
return err
}
return Success(c, "")
}
func (api CredentialApi) GenPrivateKeyEndpoint(c echo.Context) error {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
})
return Success(c, string(privateKeyPEM))
}
func (api CredentialApi) GetPublicKeyEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := service.CredentialService.FindByIdAndDecrypt(context.TODO(), id)
if err != nil {
return err
}
if item.PrivateKey == "" || item.PrivateKey == "-" {
return Success(c, "")
}
block, _ := pem.Decode([]byte(item.PrivateKey))
if block == nil {
return Success(c, "")
}
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return Success(c, "")
}
publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
return Success(c, "")
}
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
})
return Success(c, string(publicKeyPEM))
}
func (api CredentialApi) DecryptedEndpoint(c echo.Context) error {
id := c.Param("id")
securityToken := c.QueryParam("securityToken")
_, ok := cache.TokenManager.Get(securityToken)
if !ok {
return Fail(c, 401, "invalid security token")
}
item, err := service.CredentialService.FindByIdAndDecrypt(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
+136
View File
@@ -0,0 +1,136 @@
package api
import (
"context"
"next-terminal/server/common/nt"
"next-terminal/server/repository"
"time"
"github.com/labstack/echo/v4"
)
type DashboardApi struct{}
func (api DashboardApi) GetTimeCounterEndpoint(c echo.Context) error {
var (
totalUser int64
onlineUser int64
countOnlineSession int64
totalAsset int64
activeAsset int64
failLoginCount int64
)
totalUser, _ = repository.UserRepository.Count(context.TODO())
onlineUser, _ = repository.UserRepository.CountOnlineUser(context.TODO())
countOnlineSession, _ = repository.SessionRepository.CountOnlineSession(context.TODO())
totalAsset, _ = repository.AssetRepository.Count(context.TODO())
activeAsset, _ = repository.AssetRepository.CountByActive(context.TODO(), true)
failLoginCount, _ = repository.LoginLogRepository.CountByState(context.TODO(), "0")
gatewayList, _ := repository.GatewayRepository.FindAll(context.TODO())
gatewayActiveCount := int64(len(gatewayList))
counter := map[string]interface{}{
"loginFailedTimes": failLoginCount,
"userOnlineCount": onlineUser,
"sessionOnlineCount": countOnlineSession,
"sessionTotalCount": 0,
"userTotalCount": totalUser,
"assetActiveCount": activeAsset,
"assetTotalCount": totalAsset,
"websiteActiveCount": 0,
"websiteTotalCount": 0,
"gatewayActiveCount": gatewayActiveCount,
"gatewayTotalCount": gatewayActiveCount,
}
return Success(c, counter)
}
func (api DashboardApi) GetDateCounterV2Endpoint(c echo.Context) error {
d := c.QueryParam("d")
var days = 7
if d == "month" {
days = 30
}
now := time.Now()
lastDate := now.AddDate(0, 0, -days)
loginLogCounters, err := repository.LoginLogRepository.CountWithGroupByLoginTime(context.TODO(), lastDate)
if err != nil {
return err
}
userCounters, err := repository.LoginLogRepository.CountWithGroupByLoginTimeAndUsername(context.TODO(), lastDate)
if err != nil {
return err
}
sessionCounters, err := repository.SessionRepository.CountWithGroupByLoginTime(context.TODO(), lastDate)
if err != nil {
return err
}
var counters []map[string]interface{}
for i := 0; i < days; i++ {
day := lastDate.AddDate(0, 0, i).Format("2006-01-02")
item := map[string]interface{}{
"date": day,
"login": int64(0),
"user": int64(0),
"asset": int64(0),
}
for _, counter := range loginLogCounters {
if counter.Date == day {
item["login"] = counter.Value
break
}
}
for _, counter := range userCounters {
if counter.Date == day {
item["user"] = counter.Value
break
}
}
for _, counter := range sessionCounters {
if counter.Date == day {
item["asset"] = counter.Value
break
}
}
counters = append(counters, item)
}
return Success(c, counters)
}
func (api DashboardApi) GetAssetTypesEndpoint(c echo.Context) error {
var (
ssh int64
rdp int64
vnc int64
telnet int64
kubernetes int64
http int64
)
ssh, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.SSH)
rdp, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.RDP)
vnc, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.VNC)
telnet, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.Telnet)
kubernetes, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.K8s)
types := []map[string]interface{}{
{"type": "SSH", "value": ssh},
{"type": "RDP", "value": rdp},
{"type": "VNC", "value": vnc},
{"type": "TELNET", "value": telnet},
{"type": "KUBERNETES", "value": kubernetes},
{"type": "HTTP", "value": http},
}
return Success(c, types)
}
+74
View File
@@ -0,0 +1,74 @@
package api
import (
"next-terminal/server/common/maps"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type DatabaseAssetApi struct{}
func (api DatabaseAssetApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api DatabaseAssetApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"items": []interface{}{},
"total": 0,
})
}
func (api DatabaseAssetApi) CreateEndpoint(c echo.Context) error {
var item map[string]interface{}
if err := c.Bind(&item); err != nil {
return err
}
item["id"] = utils.LongUUID()
return Success(c, item)
}
func (api DatabaseAssetApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item map[string]interface{}
if err := c.Bind(&item); err != nil {
return err
}
item["id"] = id
return Success(c, item)
}
func (api DatabaseAssetApi) DeleteEndpoint(c echo.Context) error {
id := c.Param("id")
_ = id
return Success(c, nil)
}
func (api DatabaseAssetApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
_ = id
return Success(c, maps.Map{
"id": id,
"name": "MySQL Server",
"type": "mysql",
"host": "localhost",
"port": 3306,
"database": "test_db",
"username": "root",
"description": "Test database",
"status": "active",
"statusText": "Active",
"createdAt": 1700000000000,
"updatedAt": 1700000000000,
})
}
func (api DatabaseAssetApi) DecryptEndpoint(c echo.Context) error {
id := c.Param("id")
_ = id
return Success(c, maps.Map{
"id": id,
"password": "decrypted_password",
})
}
+86
View File
@@ -0,0 +1,86 @@
package api
import (
"next-terminal/server/common/maps"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type DepartmentApi struct{}
func (api DepartmentApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api DepartmentApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"items": []interface{}{},
"total": 0,
})
}
func (api DepartmentApi) CreateEndpoint(c echo.Context) error {
var item map[string]interface{}
if err := c.Bind(&item); err != nil {
return err
}
item["id"] = utils.LongUUID()
return Success(c, item)
}
func (api DepartmentApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item map[string]interface{}
if err := c.Bind(&item); err != nil {
return err
}
item["id"] = id
return Success(c, item)
}
func (api DepartmentApi) DeleteEndpoint(c echo.Context) error {
id := c.Param("id")
_ = id
return Success(c, nil)
}
func (api DepartmentApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
_ = id
return Success(c, maps.Map{
"id": id,
"name": "Default",
"parentId": "",
})
}
func (api DepartmentApi) GetTreeEndpoint(c echo.Context) error {
tree := []map[string]interface{}{
{
"title": "Default",
"key": "default",
"value": "default",
"children": []interface{}{},
},
}
return Success(c, tree)
}
func (api DepartmentApi) GetDepartmentUsersEndpoint(c echo.Context) error {
departmentId := c.Param("id")
_ = departmentId
return Success(c, []string{})
}
func (api DepartmentApi) SetDepartmentUsersEndpoint(c echo.Context) error {
departmentId := c.Param("id")
_ = departmentId
return Success(c, nil)
}
func (api DepartmentApi) RemoveUsersFromDepartmentEndpoint(c echo.Context) error {
departmentId := c.Param("id")
_ = departmentId
return Success(c, nil)
}
+433
View File
@@ -0,0 +1,433 @@
package api
import (
"fmt"
"next-terminal/server/common/maps"
"next-terminal/server/global/session"
"next-terminal/server/log"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/labstack/echo/v4"
"github.com/pkg/sftp"
)
type FileSystemApi struct{}
func (api FileSystemApi) LsEndpoint(c echo.Context) error {
sessionId := c.Param("id")
dir := c.QueryParam("dir")
hiddenFileVisible := c.QueryParam("hiddenFileVisible") == "true"
log.Debug("FileSystem Ls: sessionId=" + sessionId + " dir=" + dir)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
log.Debug("FileSystem Ls: failed to create SFTP client: " + err.Error())
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
files, err := sftpClient.ReadDir(dir)
if err != nil {
log.Debug("FileSystem Ls: failed to read directory: " + err.Error())
return Fail(c, -1, "failed to read directory: "+err.Error())
}
var result []maps.Map
for _, f := range files {
name := f.Name()
if !hiddenFileVisible && strings.HasPrefix(name, ".") {
continue
}
result = append(result, maps.Map{
"name": name,
"size": f.Size(),
"modTime": f.ModTime().UnixMilli(),
"path": path.Join(dir, name),
"mode": f.Mode().String(),
"isDir": f.IsDir(),
"isLink": f.Mode()&os.ModeSymlink != 0,
})
}
return Success(c, result)
}
func (api FileSystemApi) MkdirEndpoint(c echo.Context) error {
sessionId := c.Param("id")
dir := c.QueryParam("dir")
log.Debug("FileSystem Mkdir: sessionId=" + sessionId + " dir=" + dir)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
err = sftpClient.MkdirAll(dir)
if err != nil {
return Fail(c, -1, "failed to create directory: "+err.Error())
}
return Success(c, nil)
}
func (api FileSystemApi) TouchEndpoint(c echo.Context) error {
sessionId := c.Param("id")
filename := c.QueryParam("filename")
log.Debug("FileSystem Touch: sessionId=" + sessionId + " filename=" + filename)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
f, err := sftpClient.Create(filename)
if err != nil {
return Fail(c, -1, "failed to create file: "+err.Error())
}
f.Close()
return Success(c, nil)
}
func (api FileSystemApi) RmEndpoint(c echo.Context) error {
sessionId := c.Param("id")
filename := c.QueryParam("filename")
log.Debug("FileSystem Rm: sessionId=" + sessionId + " filename=" + filename)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
f, err := sftpClient.Stat(filename)
if err != nil {
return Fail(c, -1, "file not found: "+err.Error())
}
if f.IsDir() {
err = sftpClient.RemoveDirectory(filename)
if err != nil {
err = api.removeRecursive(sftpClient, filename)
if err != nil {
return Fail(c, -1, "failed to remove directory: "+err.Error())
}
}
} else {
err = sftpClient.Remove(filename)
if err != nil {
return Fail(c, -1, "failed to remove file: "+err.Error())
}
}
return Success(c, nil)
}
func (api FileSystemApi) removeRecursive(sftpClient *sftp.Client, dirPath string) error {
files, err := sftpClient.ReadDir(dirPath)
if err != nil {
return err
}
for _, f := range files {
fullPath := path.Join(dirPath, f.Name())
if f.IsDir() {
err = api.removeRecursive(sftpClient, fullPath)
if err != nil {
return err
}
} else {
err = sftpClient.Remove(fullPath)
if err != nil {
return err
}
}
}
return sftpClient.RemoveDirectory(dirPath)
}
func (api FileSystemApi) RenameEndpoint(c echo.Context) error {
sessionId := c.Param("id")
oldName := c.QueryParam("oldName")
newName := c.QueryParam("newName")
log.Debug("FileSystem Rename: sessionId=" + sessionId + " oldName=" + oldName + " newName=" + newName)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
err = sftpClient.Rename(oldName, newName)
if err != nil {
return Fail(c, -1, "failed to rename: "+err.Error())
}
return Success(c, nil)
}
func (api FileSystemApi) EditEndpoint(c echo.Context) error {
sessionId := c.Param("id")
var req struct {
Filename string `json:"filename"`
FileContent string `json:"fileContent"`
}
if err := c.Bind(&req); err != nil {
return err
}
log.Debug("FileSystem Edit: sessionId=" + sessionId + " filename=" + req.Filename)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
f, err := sftpClient.OpenFile(req.Filename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE)
if err != nil {
return Fail(c, -1, "failed to open file: "+err.Error())
}
defer f.Close()
_, err = f.Write([]byte(req.FileContent))
if err != nil {
return Fail(c, -1, "failed to write file: "+err.Error())
}
return Success(c, nil)
}
func (api FileSystemApi) ReadEndpoint(c echo.Context) error {
sessionId := c.Param("id")
filename := c.QueryParam("filename")
log.Debug("FileSystem Read: sessionId=" + sessionId + " filename=" + filename)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
f, err := sftpClient.Open(filename)
if err != nil {
return Fail(c, -1, "failed to open file: "+err.Error())
}
defer f.Close()
content, err := readFileContent(f, 10*1024*1024)
if err != nil {
return Fail(c, -1, "failed to read file: "+err.Error())
}
return Success(c, maps.Map{
"content": content,
"path": filename,
})
}
func readFileContent(f *sftp.File, maxSize int64) (string, error) {
stat, err := f.Stat()
if err != nil {
return "", err
}
if stat.Size() > maxSize {
return "", fmt.Errorf("file too large (max %d bytes)", maxSize)
}
buf := make([]byte, stat.Size())
_, err = f.Read(buf)
if err != nil {
return "", err
}
return string(buf), nil
}
func (api FileSystemApi) ChmodEndpoint(c echo.Context) error {
sessionId := c.Param("id")
filename := c.QueryParam("filename")
modeStr := c.QueryParam("mode")
log.Debug("FileSystem Chmod: sessionId=" + sessionId + " filename=" + filename + " mode=" + modeStr)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
mode, err := strconv.ParseUint(modeStr, 10, 32)
if err != nil {
return Fail(c, -1, "invalid mode: "+err.Error())
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
err = sftpClient.Chmod(filename, os.FileMode(mode))
if err != nil {
return Fail(c, -1, "failed to chmod: "+err.Error())
}
return Success(c, nil)
}
func (api FileSystemApi) UploadProgressEndpoint(c echo.Context) error {
sessionId := c.Param("id")
id := c.QueryParam("id")
log.Debug("FileSystem UploadProgress: sessionId=" + sessionId + " id=" + id)
return Success(c, maps.Map{
"total": 0,
"written": 0,
"percent": 100,
"speed": 0,
"elapsedTime": 0,
"isCompleted": true,
})
}
func (api FileSystemApi) DownloadEndpoint(c echo.Context) error {
sessionId := c.Param("id")
filename := c.QueryParam("filename")
log.Debug("FileSystem Download: sessionId=" + sessionId + " filename=" + filename)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
f, err := sftpClient.Open(filename)
if err != nil {
return Fail(c, -1, "failed to open file: "+err.Error())
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
return Fail(c, -1, "failed to stat file: "+err.Error())
}
c.Response().Header().Set("Content-Disposition", "attachment; filename="+path.Base(filename))
c.Response().Header().Set("Content-Type", "application/octet-stream")
c.Response().Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10))
return c.Stream(200, "application/octet-stream", f)
}
func (api FileSystemApi) UploadEndpoint(c echo.Context) error {
sessionId := c.Param("id")
destDir := c.QueryParam("dir")
log.Debug("FileSystem Upload: sessionId=" + sessionId + " dir=" + destDir)
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Fail(c, -1, "session not found or SSH connection closed")
}
file, err := c.FormFile("file")
if err != nil {
return Fail(c, -1, "failed to get upload file: "+err.Error())
}
src, err := file.Open()
if err != nil {
return Fail(c, -1, "failed to open upload file: "+err.Error())
}
defer src.Close()
sftpClient, err := sftp.NewClient(sess.NextTerminal.SshClient)
if err != nil {
return Fail(c, -1, "failed to create SFTP client: "+err.Error())
}
defer sftpClient.Close()
destPath := path.Join(destDir, file.Filename)
dst, err := sftpClient.Create(destPath)
if err != nil {
return Fail(c, -1, "failed to create remote file: "+err.Error())
}
defer dst.Close()
buf := make([]byte, 32*1024)
for {
n, err := src.Read(buf)
if n > 0 {
_, writeErr := dst.Write(buf[:n])
if writeErr != nil {
return Fail(c, -1, "failed to write remote file: "+writeErr.Error())
}
}
if err != nil {
break
}
}
return Success(c, maps.Map{
"path": destPath,
"size": file.Size,
"timestamp": time.Now().UnixMilli(),
})
}
+80
View File
@@ -0,0 +1,80 @@
package api
import (
"context"
"strconv"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type GatewayGroupApi struct{}
func (api GatewayGroupApi) AllEndpoint(c echo.Context) error {
items, err := repository.GatewayGroupRepository.FindAll(context.TODO())
if err != nil {
return err
}
return Success(c, items)
}
func (api GatewayGroupApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
keyword := c.QueryParam("keyword")
items, total, err := repository.GatewayGroupRepository.Find(context.TODO(), pageIndex, pageSize, keyword)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api GatewayGroupApi) CreateEndpoint(c echo.Context) error {
var item model.GatewayGroup
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
if err := repository.GatewayGroupRepository.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api GatewayGroupApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.GatewayGroup
if err := c.Bind(&item); err != nil {
return err
}
if err := repository.GatewayGroupRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
return Success(c, nil)
}
func (api GatewayGroupApi) DeleteEndpoint(c echo.Context) error {
id := c.Param("id")
if err := repository.GatewayGroupRepository.DeleteById(context.TODO(), id); err != nil {
return err
}
return Success(c, nil)
}
func (api GatewayGroupApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.GatewayGroupRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
+342
View File
@@ -0,0 +1,342 @@
package api
import (
"context"
"fmt"
"net/http"
"next-terminal/server/common/guacamole"
"next-terminal/server/common/nt"
"path"
"strconv"
"next-terminal/server/config"
"next-terminal/server/global/session"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/gorilla/websocket"
"github.com/labstack/echo/v4"
)
const (
TunnelClosed int = -1
Normal int = 0
NotFoundSession int = 800
NewTunnelError int = 801
ForcedDisconnect int = 802
AccessGatewayUnAvailable int = 803
AccessGatewayCreateError int = 804
AssetNotActive int = 805
NewSshClientError int = 806
)
var UpGrader = websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
CheckOrigin: func(r *http.Request) bool {
return true
},
Subprotocols: []string{"guacamole"},
}
type GuacamoleApi struct {
}
func (api GuacamoleApi) Guacamole(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
log.Warn("升级为WebSocket协议失败", log.NamedError("err", err))
return err
}
ctx := context.TODO()
width := c.QueryParam("width")
height := c.QueryParam("height")
dpi := c.QueryParam("dpi")
sessionId := c.Param("id")
intWidth, _ := strconv.Atoi(width)
intHeight, _ := strconv.Atoi(height)
configuration := guacamole.NewConfiguration()
propertyMap := repository.PropertyRepository.FindAllMap(ctx)
configuration.SetParameter("width", width)
configuration.SetParameter("height", height)
configuration.SetParameter("dpi", dpi)
s, err := service.SessionService.FindByIdAndDecrypt(ctx, sessionId)
if err != nil {
return err
}
api.setConfig(propertyMap, s, configuration)
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
g, err := service.GatewayService.GetGatewayById(s.AccessGatewayId)
if err != nil {
guacamole.Disconnect(ws, AccessGatewayUnAvailable, "获取接入网关失败:"+err.Error())
return nil
}
defer g.CloseSshTunnel(s.ID)
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, s.IP, s.Port)
if err != nil {
guacamole.Disconnect(ws, AccessGatewayCreateError, "创建SSH隧道失败:"+err.Error())
return nil
}
s.IP = exposedIP
s.Port = exposedPort
}
configuration.SetParameter("hostname", s.IP)
configuration.SetParameter("port", strconv.Itoa(s.Port))
// 加载资产配置的属性,优先级比全局配置的高,因此最后加载,覆盖掉全局配置
attributes, err := repository.AssetRepository.FindAssetAttrMapByAssetId(ctx, s.AssetId)
if err != nil {
return err
}
if len(attributes) > 0 {
api.setAssetConfig(attributes, s, configuration)
}
for name := range configuration.Parameters {
// 替换数据库空格字符串占位符为真正的空格
if configuration.Parameters[name] == "-" {
configuration.Parameters[name] = ""
}
}
addr := config.GlobalCfg.Guacd.Hostname + ":" + strconv.Itoa(config.GlobalCfg.Guacd.Port)
asset := fmt.Sprintf("%s:%s", configuration.GetParameter("hostname"), configuration.GetParameter("port"))
log.Debug("新建 guacd 会话", log.String("sessionId", sessionId), log.String("addr", addr), log.String("asset", asset))
guacdTunnel, err := guacamole.NewTunnel(addr, configuration)
if err != nil {
guacamole.Disconnect(ws, NewTunnelError, err.Error())
log.Error("建立连接失败", log.String("sessionId", sessionId), log.NamedError("err", err))
return err
}
nextSession := &session.Session{
ID: sessionId,
Protocol: s.Protocol,
Mode: s.Mode,
WebSocket: ws,
GuacdTunnel: guacdTunnel,
}
if configuration.Protocol == nt.SSH {
nextTerminal, err := CreateNextTerminalBySession(s)
if err != nil {
guacamole.Disconnect(ws, NewSshClientError, "建立SSH客户端失败: "+err.Error())
log.Debug("建立 ssh 客户端失败", log.String("sessionId", sessionId), log.NamedError("err", err))
return err
}
nextSession.NextTerminal = nextTerminal
}
nextSession.Observer = session.NewObserver(sessionId)
session.GlobalSessionManager.Add(nextSession)
sess := model.Session{
ConnectionId: guacdTunnel.UUID,
Width: intWidth,
Height: intHeight,
Status: nt.Connecting,
Recording: configuration.GetParameter(guacamole.RecordingPath),
}
if sess.Recording == "" {
// 未录屏时无需审计
sess.Reviewed = true
}
// 创建新会话
log.Debug("新建会话成功", log.String("sessionId", sessionId))
if err := repository.SessionRepository.UpdateById(ctx, &sess, sessionId); err != nil {
return err
}
guacamoleHandler := NewGuacamoleHandler(ws, guacdTunnel)
guacamoleHandler.Start()
defer guacamoleHandler.Stop()
for {
_, message, err := ws.ReadMessage()
if err != nil {
log.Debug("WebSocket已关闭", log.String("sessionId", sessionId), log.NamedError("err", err))
// guacdTunnel.Read() 会阻塞,所以要先把guacdTunnel客户端关闭,才能退出Guacd循环
_ = guacdTunnel.Close()
service.SessionService.CloseSessionById(sessionId, Normal, "用户正常退出")
return nil
}
_, err = guacdTunnel.WriteAndFlush(message)
if err != nil {
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
return nil
}
}
}
func (api GuacamoleApi) setAssetConfig(attributes map[string]string, s model.Session, configuration *guacamole.Configuration) {
for key, value := range attributes {
if guacamole.DrivePath == key {
// 忽略该参数
continue
}
if guacamole.EnableDrive == key && value == "true" {
storageId := attributes[guacamole.DrivePath]
if storageId == "" || storageId == "-" {
// 默认空间ID和用户ID相同
storageId = s.Creator
}
realPath := path.Join(service.StorageService.GetBaseDrivePath(), storageId)
configuration.SetParameter(guacamole.EnableDrive, "true")
configuration.SetParameter(guacamole.DriveName, "Filesystem")
configuration.SetParameter(guacamole.DrivePath, realPath)
} else {
configuration.SetParameter(key, value)
}
}
}
func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
log.Warn("升级为WebSocket协议失败", log.NamedError("err", err))
return err
}
ctx := context.TODO()
sessionId := c.Param("id")
s, err := repository.SessionRepository.FindById(ctx, sessionId)
if err != nil {
return err
}
if s.Status != nt.Connected {
guacamole.Disconnect(ws, AssetNotActive, "会话离线")
return nil
}
connectionId := s.ConnectionId
configuration := guacamole.NewConfiguration()
configuration.ConnectionID = connectionId
sessionId = s.ID
configuration.SetParameter("width", strconv.Itoa(s.Width))
configuration.SetParameter("height", strconv.Itoa(s.Height))
configuration.SetParameter("dpi", "96")
configuration.SetReadOnlyMode()
addr := config.GlobalCfg.Guacd.Hostname + ":" + strconv.Itoa(config.GlobalCfg.Guacd.Port)
guacdTunnel, err := guacamole.NewTunnel(addr, configuration)
if err != nil {
guacamole.Disconnect(ws, NewTunnelError, err.Error())
return err
}
nextSession := &session.Session{
ID: sessionId,
Protocol: s.Protocol,
Mode: s.Mode,
WebSocket: ws,
GuacdTunnel: guacdTunnel,
}
// 要监控会话
forObsSession := session.GlobalSessionManager.GetById(sessionId)
if forObsSession == nil {
guacamole.Disconnect(ws, NotFoundSession, "获取会话失败")
return nil
}
nextSession.ID = utils.UUID()
forObsSession.Observer.Add(nextSession)
guacamoleHandler := NewGuacamoleHandler(ws, guacdTunnel)
guacamoleHandler.Start()
defer guacamoleHandler.Stop()
for {
_, message, err := ws.ReadMessage()
if err != nil {
// guacdTunnel.Read() 会阻塞,所以要先把guacdTunnel客户端关闭,才能退出Guacd循环
_ = guacdTunnel.Close()
observerId := nextSession.ID
forObsSession.Observer.Del(observerId)
return nil
}
_, err = guacdTunnel.WriteAndFlush(message)
if err != nil {
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
return nil
}
}
}
func (api GuacamoleApi) setConfig(propertyMap map[string]string, s model.Session, configuration *guacamole.Configuration) {
if propertyMap[guacamole.EnableRecording] == "true" {
configuration.SetParameter(guacamole.RecordingPath, path.Join(config.GlobalCfg.Guacd.Recording, s.ID))
configuration.SetParameter(guacamole.CreateRecordingPath, "true")
} else {
configuration.SetParameter(guacamole.RecordingPath, "")
}
configuration.Protocol = s.Protocol
switch configuration.Protocol {
case "rdp":
configuration.SetParameter("username", s.Username)
configuration.SetParameter("password", s.Password)
configuration.SetParameter("security", "any")
configuration.SetParameter("ignore-cert", "true")
configuration.SetParameter("create-drive-path", "true")
configuration.SetParameter("resize-method", "reconnect")
configuration.SetParameter(guacamole.EnableWallpaper, propertyMap[guacamole.EnableWallpaper])
configuration.SetParameter(guacamole.EnableTheming, propertyMap[guacamole.EnableTheming])
configuration.SetParameter(guacamole.EnableFontSmoothing, propertyMap[guacamole.EnableFontSmoothing])
configuration.SetParameter(guacamole.EnableFullWindowDrag, propertyMap[guacamole.EnableFullWindowDrag])
configuration.SetParameter(guacamole.EnableDesktopComposition, propertyMap[guacamole.EnableDesktopComposition])
configuration.SetParameter(guacamole.EnableMenuAnimations, propertyMap[guacamole.EnableMenuAnimations])
configuration.SetParameter(guacamole.DisableBitmapCaching, propertyMap[guacamole.DisableBitmapCaching])
configuration.SetParameter(guacamole.DisableOffscreenCaching, propertyMap[guacamole.DisableOffscreenCaching])
configuration.SetParameter(guacamole.ColorDepth, propertyMap[guacamole.ColorDepth])
configuration.SetParameter(guacamole.ForceLossless, propertyMap[guacamole.ForceLossless])
configuration.SetParameter(guacamole.PreConnectionId, propertyMap[guacamole.PreConnectionId])
configuration.SetParameter(guacamole.PreConnectionBlob, propertyMap[guacamole.PreConnectionBlob])
case "ssh":
if len(s.PrivateKey) > 0 && s.PrivateKey != "-" {
configuration.SetParameter("username", s.Username)
configuration.SetParameter("private-key", s.PrivateKey)
configuration.SetParameter("passphrase", s.Passphrase)
} else {
configuration.SetParameter("username", s.Username)
configuration.SetParameter("password", s.Password)
}
configuration.SetParameter(guacamole.FontSize, propertyMap[guacamole.FontSize])
configuration.SetParameter(guacamole.FontName, propertyMap[guacamole.FontName])
configuration.SetParameter(guacamole.ColorScheme, propertyMap[guacamole.ColorScheme])
configuration.SetParameter(guacamole.Backspace, propertyMap[guacamole.Backspace])
configuration.SetParameter(guacamole.TerminalType, propertyMap[guacamole.TerminalType])
case "vnc":
configuration.SetParameter("username", s.Username)
configuration.SetParameter("password", s.Password)
case "telnet":
configuration.SetParameter("username", s.Username)
configuration.SetParameter("password", s.Password)
configuration.SetParameter(guacamole.FontSize, propertyMap[guacamole.FontSize])
configuration.SetParameter(guacamole.FontName, propertyMap[guacamole.FontName])
configuration.SetParameter(guacamole.ColorScheme, propertyMap[guacamole.ColorScheme])
configuration.SetParameter(guacamole.Backspace, propertyMap[guacamole.Backspace])
configuration.SetParameter(guacamole.TerminalType, propertyMap[guacamole.TerminalType])
case "kubernetes":
configuration.SetParameter(guacamole.FontSize, propertyMap[guacamole.FontSize])
configuration.SetParameter(guacamole.FontName, propertyMap[guacamole.FontName])
configuration.SetParameter(guacamole.ColorScheme, propertyMap[guacamole.ColorScheme])
configuration.SetParameter(guacamole.Backspace, propertyMap[guacamole.Backspace])
configuration.SetParameter(guacamole.TerminalType, propertyMap[guacamole.TerminalType])
default:
}
}
+53
View File
@@ -0,0 +1,53 @@
package api
import (
"context"
"next-terminal/server/common/guacamole"
"github.com/gorilla/websocket"
)
type GuacamoleHandler struct {
ws *websocket.Conn
tunnel *guacamole.Tunnel
ctx context.Context
cancel context.CancelFunc
}
func NewGuacamoleHandler(ws *websocket.Conn, tunnel *guacamole.Tunnel) *GuacamoleHandler {
ctx, cancel := context.WithCancel(context.Background())
return &GuacamoleHandler{
ws: ws,
tunnel: tunnel,
ctx: ctx,
cancel: cancel,
}
}
func (r GuacamoleHandler) Start() {
go func() {
for {
select {
case <-r.ctx.Done():
return
default:
instruction, err := r.tunnel.Read()
if err != nil {
guacamole.Disconnect(r.ws, TunnelClosed, "远程连接已关闭")
return
}
if len(instruction) == 0 {
continue
}
err = r.ws.WriteMessage(websocket.TextMessage, instruction)
if err != nil {
return
}
}
}
}()
}
func (r GuacamoleHandler) Stop() {
r.cancel()
}
+133
View File
@@ -0,0 +1,133 @@
package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type JobApi struct{}
func (api JobApi) JobCreateEndpoint(c echo.Context) error {
var item model.Job
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
if err := service.JobService.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api JobApi) JobPagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
status := c.QueryParam("status")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.JobRepository.Find(context.TODO(), pageIndex, pageSize, name, status, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api JobApi) JobUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.Job
if err := c.Bind(&item); err != nil {
return err
}
item.ID = id
if err := service.JobService.UpdateById(&item); err != nil {
return err
}
return Success(c, nil)
}
func (api JobApi) JobChangeStatusEndpoint(c echo.Context) error {
id := c.Param("id")
status := c.QueryParam("status")
if err := service.JobService.ChangeStatusById(id, status); err != nil {
return err
}
return Success(c, "")
}
func (api JobApi) JobExecEndpoint(c echo.Context) error {
id := c.Param("id")
if err := service.JobService.ExecJobById(id); err != nil {
return err
}
return Success(c, "")
}
func (api JobApi) JobDeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
for i := range split {
jobId := split[i]
if err := service.JobService.DeleteJobById(jobId); err != nil {
return err
}
}
return Success(c, nil)
}
func (api JobApi) JobGetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.JobRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
func (api JobApi) JobGetLogsEndpoint(c echo.Context) error {
id := c.Param("id")
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
items, total, err := repository.JobLogRepository.FindByJobId(context.TODO(), id, pageIndex, pageSize)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api JobApi) JobDeleteLogsEndpoint(c echo.Context) error {
id := c.Param("id")
if err := repository.JobLogRepository.DeleteByJobId(context.TODO(), id); err != nil {
return err
}
return Success(c, "")
}
+74
View File
@@ -0,0 +1,74 @@
package api
import (
"context"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"strconv"
"strings"
"github.com/labstack/echo/v4"
)
type LoginLogApi struct{}
func (api LoginLogApi) LoginLogPagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
username := c.QueryParam("username")
clientIp := c.QueryParam("clientIp")
state := c.QueryParam("state")
items, total, err := repository.LoginLogRepository.Find(context.TODO(), pageIndex, pageSize, username, clientIp, state)
if err != nil {
return err
}
for i := range items {
items[i].Success = items[i].State == "1"
items[i].UserAgent = parseUserAgent(items[i].ClientUserAgent)
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func parseUserAgent(ua string) *model.UserAgent {
if ua == "" {
return nil
}
return &model.UserAgent{
String: ua,
}
}
func (api LoginLogApi) LoginLogDeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
tokens := strings.Split(ids, ",")
if err := service.UserService.DeleteLoginLogs(tokens); err != nil {
return err
}
return Success(c, nil)
}
func (api LoginLogApi) LoginLogClearEndpoint(c echo.Context) error {
loginLogs, err := repository.LoginLogRepository.FindAllLoginLogs(context.TODO())
if err != nil {
return err
}
var tokens = make([]string, 0)
for i := range loginLogs {
tokens = append(tokens, loginLogs[i].ID)
}
if err := service.UserService.DeleteLoginLogs(tokens); err != nil {
return err
}
return Success(c, nil)
}
+143
View File
@@ -0,0 +1,143 @@
package api
import (
"context"
"strconv"
"strings"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type LoginPolicyApi struct{}
func (api LoginPolicyApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
userId := c.QueryParam("userId")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.LoginPolicyRepository.Find(context.TODO(), pageIndex, pageSize, name, userId, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api LoginPolicyApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := service.LoginPolicyService.FindById(context.Background(), id)
if err != nil {
return err
}
return Success(c, item)
}
func (api LoginPolicyApi) CreateEndpoint(c echo.Context) error {
var item model.LoginPolicy
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
if err := service.LoginPolicyService.Create(context.Background(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api LoginPolicyApi) DeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
if err := service.LoginPolicyService.DeleteByIds(context.Background(), split); err != nil {
return err
}
return Success(c, nil)
}
func (api LoginPolicyApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.LoginPolicy
if err := c.Bind(&item); err != nil {
return err
}
if err := service.LoginPolicyService.UpdateById(context.Background(), &item, id); err != nil {
return err
}
return Success(c, "")
}
func (api LoginPolicyApi) GetUserPageEndpoint(c echo.Context) error {
id := c.Param("id")
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
username := c.QueryParam("username")
nickname := c.QueryParam("nickname")
mail := c.QueryParam("mail")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.UserRepository.Find(context.TODO(), pageIndex, pageSize, username, nickname, mail, "", id, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api LoginPolicyApi) BindEndpoint(c echo.Context) error {
var items []model.LoginPolicyUserRef
if err := c.Bind(&items); err != nil {
return err
}
id := c.Param("id")
if err := service.LoginPolicyService.Bind(context.Background(), id, items); err != nil {
return err
}
return Success(c, "")
}
func (api LoginPolicyApi) UnbindEndpoint(c echo.Context) error {
var items []model.LoginPolicyUserRef
if err := c.Bind(&items); err != nil {
return err
}
id := c.Param("id")
if err := service.LoginPolicyService.Unbind(context.Background(), id, items); err != nil {
return err
}
return Success(c, "")
}
func (api LoginPolicyApi) GetUserIdEndpoint(c echo.Context) error {
id := c.Param("id")
refs, err := repository.LoginPolicyUserRefRepository.FindByLoginPolicyId(context.Background(), id)
if err != nil {
return err
}
var ids = make([]string, 0)
for _, ref := range refs {
ids = append(ids, ref.UserId)
}
return Success(c, ids)
}
+58
View File
@@ -0,0 +1,58 @@
package api
import (
"context"
"encoding/base64"
"strings"
"next-terminal/server/model"
"next-terminal/server/repository"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
)
type LogoApi struct{}
func (api LogoApi) LogosEndpoint(c echo.Context) error {
logos := repository.LogoRepository.FindAll(context.TODO())
return Success(c, logos)
}
func (api LogoApi) UploadEndpoint(c echo.Context) error {
file, err := c.FormFile("file")
if err != nil {
return err
}
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
buf := make([]byte, file.Size)
n, _ := src.Read(buf)
data := base64.StdEncoding.EncodeToString(buf[:n])
ext := strings.ToLower(file.Filename[strings.LastIndex(file.Filename, "."):])
name := uuid.New().String() + ext
logo := model.Logo{
ID: uuid.New().String(),
Name: name,
Data: "data:image/" + ext + ";base64," + data,
Deletable: true,
}
if err := repository.LogoRepository.Create(context.TODO(), &logo); err != nil {
return err
}
return Success(c, logo)
}
func (api LogoApi) DeleteEndpoint(c echo.Context) error {
name := c.Param("name")
if err := repository.LogoRepository.DeleteByName(context.TODO(), name); err != nil {
return err
}
return Success(c, nil)
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

+229
View File
@@ -0,0 +1,229 @@
package api
import (
"context"
"next-terminal/server/common/nt"
"next-terminal/server/dto"
"next-terminal/server/global/stat"
"next-terminal/server/repository"
"time"
"github.com/labstack/echo/v4"
)
type OverviewApi struct{}
func (api OverviewApi) OverviewCounterEndPoint(c echo.Context) error {
var (
totalUser int64
onlineUser int64
countOfflineSession int64
totalAsset int64
activeAsset int64
failLoginCount int64
)
totalUser, _ = repository.UserRepository.Count(context.TODO())
onlineUser, _ = repository.UserRepository.CountOnlineUser(context.TODO())
countOfflineSession, _ = repository.SessionRepository.CountOfflineSession(context.TODO())
totalAsset, _ = repository.AssetRepository.Count(context.TODO())
activeAsset, _ = repository.AssetRepository.CountByActive(context.TODO(), true)
failLoginCount, _ = repository.LoginLogRepository.CountByState(context.TODO(), "0")
counter := dto.Counter{
TotalUser: totalUser,
OnlineUser: onlineUser,
OfflineSession: countOfflineSession,
TotalAsset: totalAsset,
ActiveAsset: activeAsset,
FailLoginCount: failLoginCount,
}
return Success(c, counter)
}
func (api OverviewApi) OverviewAssetEndPoint(c echo.Context) error {
var (
ssh int64
rdp int64
vnc int64
telnet int64
kubernetes int64
)
ssh, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.SSH)
rdp, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.RDP)
vnc, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.VNC)
telnet, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.Telnet)
kubernetes, _ = repository.AssetRepository.CountByProtocol(context.TODO(), nt.K8s)
m := echo.Map{
"ssh": ssh,
"rdp": rdp,
"vnc": vnc,
"telnet": telnet,
"kubernetes": kubernetes,
"all": ssh + rdp + vnc + telnet + kubernetes,
}
return Success(c, m)
}
func (api OverviewApi) OverviewDateCounterEndPoint(c echo.Context) error {
d := c.QueryParam("d")
var days = 7
if d == "month" {
days = 30
}
now := time.Now()
lastDate := now.AddDate(0, 0, -days)
// 最近一月登录次数
loginLogCounters, err := repository.LoginLogRepository.CountWithGroupByLoginTime(context.TODO(), lastDate)
if err != nil {
return err
}
// 最近一月活跃用户
userCounters, err := repository.LoginLogRepository.CountWithGroupByLoginTimeAndUsername(context.TODO(), lastDate)
if err != nil {
return err
}
// 最近一月活跃资产
sessionCounters, err := repository.SessionRepository.CountWithGroupByLoginTime(context.TODO(), lastDate)
if err != nil {
return err
}
var counters []dto.DateCounter
for i := 0; i < days; i++ {
day := lastDate.AddDate(0, 0, i).Format("2006-01-02")
var exist = false
for _, counter := range loginLogCounters {
if counter.Date == day {
exist = true
counters = append(counters, dto.DateCounter{
Type: "登录次数",
Date: day,
Value: counter.Value,
})
break
}
}
if !exist {
counters = append(counters, dto.DateCounter{
Type: "登录次数",
Date: day,
Value: 0,
})
}
exist = false
for _, counter := range userCounters {
if counter.Date == day {
exist = true
counters = append(counters, dto.DateCounter{
Type: "活跃用户",
Date: day,
Value: counter.Value,
})
break
}
}
if !exist {
counters = append(counters, dto.DateCounter{
Type: "活跃用户",
Date: day,
Value: 0,
})
}
exist = false
for _, counter := range sessionCounters {
if counter.Date == day {
exist = true
counters = append(counters, dto.DateCounter{
Type: "活跃资产",
Date: day,
Value: counter.Value,
})
break
}
}
if !exist {
counters = append(counters, dto.DateCounter{
Type: "活跃资产",
Date: day,
Value: 0,
})
}
}
return Success(c, counters)
}
func (api OverviewApi) OverviewPS(c echo.Context) error {
//memoryStat, err := mem.VirtualMemory()
//if err != nil {
// return err
//}
//avgStat, err := load.Avg()
//if err != nil {
// return err
//}
//
//cpuCount, err := cpu.Counts(true)
//if err != nil {
// return err
//}
//
//percent, err := cpu.Percent(time.Second, false)
//if err != nil {
// return err
//}
//
//var bytesRead uint64 = 0
//var bytesWrite uint64 = 0
//
//diskIO, err := disk.IOCounters()
//if err != nil {
// return err
//}
//for _, v := range diskIO {
// bytesRead += v.ReadBytes
// bytesWrite += v.WriteBytes
//}
//
//var bytesSent uint64 = 0
//var bytesRecv uint64 = 0
//netIO, err := net.IOCounters(true)
//if err != nil {
// return err
//}
//for _, v := range netIO {
// bytesSent += v.BytesSent
// bytesRecv += v.BytesRecv
//}
//return Success(c, Map{
// "mem": Map{
// "total": memoryStat.Total,
// "usedPercent": memoryStat.UsedPercent,
// },
// "cpu": Map{
// "count": cpuCount,
// "loadAvg": avgStat,
// "usedPercent": percent[0],
// },
// "diskIO": Map{
// "bytesRead": bytesRead,
// "bytesWrite": bytesWrite,
// },
// "netIO": Map{
// "bytesSent": bytesSent,
// "bytesRecv": bytesRecv,
// },
//})
return Success(c, stat.SystemLoad)
}
+572
View File
@@ -0,0 +1,572 @@
package api
import (
"context"
"strconv"
"strings"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/global/session"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"github.com/labstack/echo/v4"
"golang.org/x/crypto/ssh"
)
type PortalApi struct{}
func (api PortalApi) AssetsEndpoint(c echo.Context) error {
assetType := c.QueryParam("type")
ctx := context.TODO()
var items []model.Asset
var err error
if assetType != "" {
items, err = repository.AssetRepository.FindByProtocol(ctx, assetType)
} else {
items, err = repository.AssetRepository.FindAll(ctx)
}
if err != nil {
return err
}
result := make([]maps.Map, len(items))
for i, a := range items {
result[i] = maps.Map{
"id": a.ID,
"logo": "",
"name": a.Name,
"address": a.IP + ":" + strconv.Itoa(a.Port),
"protocol": a.Protocol,
"tags": []string{},
"status": "unknown",
"type": "asset",
"groupId": "",
"users": []string{},
}
}
return Success(c, result)
}
func (api PortalApi) DatabaseAssetsEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api PortalApi) AssetsTreeEndpoint(c echo.Context) error {
protocol := c.QueryParam("protocol")
keyword := c.QueryParam("keyword")
ctx := context.TODO()
items, _ := repository.AssetRepository.FindByProtocol(ctx, protocol)
groups, _ := repository.AssetGroupRepository.FindAll(ctx)
tree := make([]maps.Map, 0)
for _, g := range groups {
node := maps.Map{
"key": g.ID,
"title": g.Name,
"isLeaf": false,
"children": buildPortalAssetChildren(items, g.ID, keyword),
}
tree = append(tree, node)
}
for _, a := range items {
if keyword != "" && !containsKeyword(a.Name, keyword) {
continue
}
node := maps.Map{
"key": a.ID,
"title": a.Name,
"isLeaf": true,
"extra": maps.Map{
"protocol": a.Protocol,
"logo": "",
"status": "unknown",
"network": a.IP,
"wolEnabled": false,
},
}
tree = append(tree, node)
}
return Success(c, tree)
}
func buildPortalAssetChildren(assets []model.Asset, groupId, keyword string) []maps.Map {
children := make([]maps.Map, 0)
for _, a := range assets {
if keyword != "" && !containsKeyword(a.Name, keyword) {
continue
}
node := maps.Map{
"key": a.ID,
"title": a.Name,
"isLeaf": true,
"extra": maps.Map{
"protocol": a.Protocol,
"logo": "",
"status": "unknown",
"network": a.IP,
"wolEnabled": false,
},
}
children = append(children, node)
}
return children
}
func containsKeyword(name, keyword string) bool {
if keyword == "" {
return true
}
for _, ch := range name {
for _, kh := range keyword {
if ch == kh {
return true
}
}
}
return false
}
func (api PortalApi) WebsitesTreeEndpoint(c echo.Context) error {
keyword := c.QueryParam("keyword")
items, _ := repository.WebsiteRepository.FindAll(context.TODO())
tree := make([]maps.Map, 0)
for _, w := range items {
if keyword != "" && !containsKeyword(w.Name, keyword) {
continue
}
node := maps.Map{
"key": w.ID,
"title": w.Name,
"isLeaf": true,
"extra": maps.Map{
"protocol": "website",
"logo": "",
"status": w.Status,
"network": w.Domain,
},
}
tree = append(tree, node)
}
return Success(c, tree)
}
func (api PortalApi) AssetsGroupTreeEndpoint(c echo.Context) error {
groups, _ := repository.AssetGroupRepository.FindAll(context.TODO())
tree := make([]maps.Map, 0)
for _, g := range groups {
node := maps.Map{
"key": g.ID,
"title": g.Name,
"isLeaf": false,
"children": []interface{}{},
}
tree = append(tree, node)
}
return Success(c, tree)
}
func (api PortalApi) WebsitesGroupTreeEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api PortalApi) AccessRequireMfaEndpoint(c echo.Context) error {
return Success(c, maps.Map{"required": false})
}
func (api PortalApi) CreateSessionEndpoint(c echo.Context) error {
var req map[string]interface{}
if err := c.Bind(&req); err != nil {
return err
}
assetId, _ := req["assetId"].(string)
account, _ := GetCurrentAccount(c)
s, err := service.SessionService.Create(c.RealIP(), assetId, nt.Native, account)
if err != nil {
return err
}
asset, _ := repository.AssetRepository.FindById(context.TODO(), assetId)
assetName := asset.Name
if assetName == "" {
assetName = assetId
}
return Success(c, maps.Map{
"id": s.ID,
"protocol": s.Protocol,
"assetName": assetName,
"strategy": maps.Map{},
"url": "",
"watermark": maps.Map{},
"readonly": false,
"idle": 3600,
"fileSystem": s.FileSystem == "1",
"width": 800,
"height": 600,
})
}
func (api PortalApi) GetSessionEndpoint(c echo.Context) error {
id := c.Param("id")
return Success(c, maps.Map{
"id": id,
"protocol": "ssh",
"assetName": id,
"strategy": maps.Map{},
"url": "",
"watermark": maps.Map{},
"readonly": false,
"idle": 3600,
"fileSystem": false,
"width": 800,
"height": 600,
})
}
func (api PortalApi) GetShareEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"enabled": false,
"passcode": "",
"url": "",
})
}
func (api PortalApi) CreateShareEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"enabled": true,
"passcode": "",
"url": "",
})
}
func (api PortalApi) CancelShareEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api PortalApi) AccessWebsiteEndpoint(c echo.Context) error {
websiteId := c.QueryParam("websiteId")
item, _ := repository.WebsiteRepository.FindById(context.TODO(), websiteId)
url := ""
if item.TargetUrl != "" {
url = item.TargetUrl
} else {
url = "http://" + item.Domain
}
return Success(c, maps.Map{"url": url})
}
func (api PortalApi) AllowWebsiteIpEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api PortalApi) WakeOnLanEndpoint(c echo.Context) error {
assetId := c.Param("id")
_ = assetId
return Success(c, maps.Map{
"delay": 5,
})
}
func (api PortalApi) PingAssetEndpoint(c echo.Context) error {
assetId := c.Param("id")
item, _ := repository.AssetRepository.FindById(context.TODO(), assetId)
return Success(c, maps.Map{
"name": item.Name,
"active": true,
"usedTime": 10,
"usedTimeStr": "10ms",
})
}
func (api PortalApi) TerminalStatsEndpoint(c echo.Context) error {
sessionId := c.Param("id")
sess := session.GlobalSessionManager.GetById(sessionId)
if sess == nil || sess.NextTerminal == nil || sess.NextTerminal.SshClient == nil {
return Success(c, maps.Map{
"info": maps.Map{
"id": "",
"name": "",
"version": "",
"arch": "",
"uptime": 0,
"hostname": "",
"upDays": 0,
},
"load": maps.Map{
"load1": "0.00",
"load5": "0.00",
"load15": "0.00",
"runningProcess": "0",
"totalProcess": "0",
},
"memory": maps.Map{
"memTotal": 0,
"memAvailable": 0,
"memFree": 0,
"memBuffers": 0,
"memCached": 0,
"memUsed": 0,
"swapTotal": 0,
"swapFree": 0,
},
"fileSystems": []interface{}{},
"network": []interface{}{},
"cpu": []interface{}{},
})
}
stats, err := getSystemStats(sess.NextTerminal.SshClient)
if err != nil {
log.Warn("Failed to get system stats", log.NamedError("error", err))
return Success(c, maps.Map{
"info": maps.Map{
"id": "",
"name": "",
"version": "",
"arch": "",
"uptime": 0,
"hostname": "",
"upDays": 0,
},
"load": maps.Map{
"load1": "0.00",
"load5": "0.00",
"load15": "0.00",
"runningProcess": "0",
"totalProcess": "0",
},
"memory": maps.Map{
"memTotal": 0,
"memAvailable": 0,
"memFree": 0,
"memBuffers": 0,
"memCached": 0,
"memUsed": 0,
"swapTotal": 0,
"swapFree": 0,
},
"fileSystems": []interface{}{},
"network": []interface{}{},
"cpu": []interface{}{},
})
}
return Success(c, stats)
}
func getSystemStats(client *ssh.Client) (maps.Map, error) {
result := maps.Map{}
// Get hostname
hostname, _ := sshExec(client, "hostname")
// Get OS info
osId, _ := sshExec(client, "cat /etc/os-release 2>/dev/null | grep '^ID=' | cut -d'=' -f2 | tr -d '\"'")
osName, _ := sshExec(client, "cat /etc/os-release 2>/dev/null | grep '^NAME=' | cut -d'=' -f2 | tr -d '\"'")
osVersion, _ := sshExec(client, "cat /etc/os-release 2>/dev/null | grep '^VERSION_ID=' | cut -d'=' -f2 | tr -d '\"'")
// Get arch
arch, _ := sshExec(client, "uname -m")
// Get uptime
uptimeStr, _ := sshExec(client, "cat /proc/uptime | awk '{print $1}'")
uptimeFloat, _ := strconv.ParseFloat(strings.TrimSpace(uptimeStr), 64)
uptime := int64(uptimeFloat)
upDays := uptime / 86400
// Get load average
loadStr, _ := sshExec(client, "cat /proc/loadavg")
loadParts := strings.Fields(loadStr)
load1, load5, load15 := "0.00", "0.00", "0.00"
if len(loadParts) >= 3 {
load1 = loadParts[0]
load5 = loadParts[1]
load15 = loadParts[2]
}
// Get process count
processStr, _ := sshExec(client, "ps aux | wc -l")
totalProcess := strings.TrimSpace(processStr)
// Get memory info
memInfo, _ := sshExec(client, "cat /proc/meminfo")
memTotal, memFree, memAvailable, memBuffers, memCached, swapTotal, swapFree := parseMemInfo(memInfo)
memUsed := memTotal - memAvailable
// Get CPU info
cpuInfo, _ := sshExec(client, "cat /proc/stat | head -1")
cpuStats := parseCpuStat(cpuInfo)
// Get filesystem info
dfOutput, _ := sshExec(client, "df -B1 | tail -n +2")
fileSystems := parseDfOutput(dfOutput)
// Get network info
netDev, _ := sshExec(client, "cat /proc/net/dev | tail -n +3")
network := parseNetDev(netDev)
result["info"] = maps.Map{
"id": strings.TrimSpace(osId),
"name": strings.TrimSpace(osName),
"version": strings.TrimSpace(osVersion),
"arch": strings.TrimSpace(arch),
"uptime": uptime,
"hostname": strings.TrimSpace(hostname),
"upDays": upDays,
}
result["load"] = maps.Map{
"load1": load1,
"load5": load5,
"load15": load15,
"runningProcess": totalProcess,
"totalProcess": totalProcess,
}
result["memory"] = maps.Map{
"memTotal": memTotal * 1024,
"memAvailable": memAvailable * 1024,
"memFree": memFree * 1024,
"memBuffers": memBuffers * 1024,
"memCached": memCached * 1024,
"memUsed": memUsed * 1024,
"swapTotal": swapTotal * 1024,
"swapFree": swapFree * 1024,
}
result["cpu"] = cpuStats
result["fileSystems"] = fileSystems
result["network"] = network
return result, nil
}
func sshExec(client *ssh.Client, cmd string) (string, error) {
session, err := client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
output, err := session.CombinedOutput(cmd)
if err != nil {
return "", err
}
return string(output), nil
}
func parseMemInfo(info string) (total, free, available, buffers, cached, swapTotal, swapFree int64) {
lines := strings.Split(info, "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) < 2 {
continue
}
key := strings.TrimSuffix(parts[0], ":")
value, _ := strconv.ParseInt(parts[1], 10, 64)
switch key {
case "MemTotal":
total = value
case "MemFree":
free = value
case "MemAvailable":
available = value
case "Buffers":
buffers = value
case "Cached":
cached = value
case "SwapTotal":
swapTotal = value
case "SwapFree":
swapFree = value
}
}
return
}
func parseCpuStat(stat string) []interface{} {
parts := strings.Fields(stat)
if len(parts) < 5 {
return []interface{}{maps.Map{"user": 0.0, "nice": 0.0, "system": 0.0}}
}
user, _ := strconv.ParseFloat(parts[1], 64)
nice, _ := strconv.ParseFloat(parts[2], 64)
system, _ := strconv.ParseFloat(parts[3], 64)
return []interface{}{
maps.Map{
"user": user,
"nice": nice,
"system": system,
},
}
}
func parseDfOutput(output string) []interface{} {
var result []interface{}
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.TrimSpace(line) == "" {
continue
}
parts := strings.Fields(line)
if len(parts) < 6 {
continue
}
total, _ := strconv.ParseInt(parts[1], 10, 64)
used, _ := strconv.ParseInt(parts[2], 10, 64)
free, _ := strconv.ParseInt(parts[3], 10, 64)
var percent float64 = 0
if total > 0 {
percent = float64(used) / float64(total)
}
result = append(result, maps.Map{
"mountPoint": parts[5],
"total": total,
"used": used,
"free": free,
"percent": percent,
})
}
return result
}
func parseNetDev(output string) []interface{} {
var result []interface{}
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.TrimSpace(line) == "" {
continue
}
parts := strings.Fields(line)
if len(parts) < 10 {
continue
}
iface := strings.TrimSuffix(parts[0], ":")
if strings.HasPrefix(iface, "lo") {
continue
}
rx, _ := strconv.ParseInt(parts[1], 10, 64)
tx, _ := strconv.ParseInt(parts[9], 10, 64)
result = append(result, maps.Map{
"iface": iface,
"rx": rx,
"tx": tx,
"rxSec": 0,
"txSec": 0,
})
}
return result
}
+64
View File
@@ -0,0 +1,64 @@
package api
import (
"context"
"crypto/rand"
"crypto/rsa"
"encoding/pem"
"crypto/x509"
"next-terminal/server/repository"
"next-terminal/server/service"
"github.com/labstack/echo/v4"
)
type PropertyApi struct{}
func (api PropertyApi) PropertyGetEndpoint(c echo.Context) error {
properties := repository.PropertyRepository.FindAllMap(context.TODO())
return Success(c, properties)
}
func (api PropertyApi) PropertyUpdateEndpoint(c echo.Context) error {
var item map[string]interface{}
if err := c.Bind(&item); err != nil {
return err
}
if err := service.PropertyService.Update(item); err != nil {
return err
}
return Success(c, nil)
}
func (api PropertyApi) GenRSAPrivateKeyEndpoint(c echo.Context) error {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
privateKeyPem := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
})
return Success(c, map[string]string{"key": string(privateKeyPem)})
}
func (api PropertyApi) SendMailEndpoint(c echo.Context) error {
var req map[string]interface{}
if err := c.Bind(&req); err != nil {
return err
}
if err := service.MailService.SendTestMail(req); err != nil {
return err
}
return Success(c, nil)
}
func (api PropertyApi) ClientIPsEndpoint(c echo.Context) error {
return Success(c, map[string]string{
"direct": c.RealIP(),
"x-real-ip": c.Request().Header.Get("X-Real-IP"),
"x-forwarded-for": c.Request().Header.Get("X-Forwarded-For"),
})
}
+100
View File
@@ -0,0 +1,100 @@
package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/service"
"strconv"
"strings"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type RoleApi struct{}
func (api RoleApi) AllEndpoint(c echo.Context) error {
items, err := repository.RoleRepository.FindAll(context.TODO())
if err != nil {
return err
}
return Success(c, items)
}
func (api RoleApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
_type := c.QueryParam("type")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.RoleRepository.Find(context.TODO(), pageIndex, pageSize, name, _type, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api RoleApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := service.RoleService.FindById(context.Background(), id)
if err != nil {
return err
}
return Success(c, item)
}
func (api RoleApi) CreateEndpoint(c echo.Context) error {
var item model.Role
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
item.Deletable = true
item.Modifiable = true
item.Type = "new"
if err := service.RoleService.Create(context.Background(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api RoleApi) DeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
if err := service.RoleService.DeleteByIds(context.Background(), split, false); err != nil {
return err
}
return Success(c, nil)
}
func (api RoleApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.Role
if err := c.Bind(&item); err != nil {
return err
}
if err := service.RoleService.UpdateById(context.Background(), &item, id, false); err != nil {
return err
}
return Success(c, "")
}
func (api RoleApi) TreeMenus(c echo.Context) error {
return Success(c, service.MenuService.GetTreeMenus())
}
+112
View File
@@ -0,0 +1,112 @@
package api
import (
"context"
"next-terminal/server/common/maps"
"strconv"
"strings"
"next-terminal/server/global/security"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type SecurityApi struct{}
func (api SecurityApi) SecurityCreateEndpoint(c echo.Context) error {
var item model.AccessSecurity
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
item.Source = "管理员添加"
if err := repository.SecurityRepository.Create(context.TODO(), &item); err != nil {
return err
}
// 更新内存中的安全规则
rule := &security.Security{
ID: item.ID,
IP: item.IP,
Rule: item.Rule,
Priority: item.Priority,
}
security.GlobalSecurityManager.Add(rule)
return Success(c, "")
}
func (api SecurityApi) SecurityPagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
ip := c.QueryParam("ip")
rule := c.QueryParam("rule")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.SecurityRepository.Find(context.TODO(), pageIndex, pageSize, ip, rule, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api SecurityApi) SecurityUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.AccessSecurity
if err := c.Bind(&item); err != nil {
return err
}
if err := repository.SecurityRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
// 更新内存中的安全规则
security.GlobalSecurityManager.Del(id)
rule := &security.Security{
ID: item.ID,
IP: item.IP,
Rule: item.Rule,
Priority: item.Priority,
}
security.GlobalSecurityManager.Add(rule)
return Success(c, nil)
}
func (api SecurityApi) SecurityDeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
for i := range split {
id := split[i]
if err := repository.SecurityRepository.DeleteById(context.TODO(), id); err != nil {
return err
}
// 更新内存中的安全规则
security.GlobalSecurityManager.Del(id)
}
return Success(c, nil)
}
func (api SecurityApi) SecurityGetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.SecurityRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
+665
View File
@@ -0,0 +1,665 @@
package api
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/global/session"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/labstack/echo/v4"
"github.com/pkg/sftp"
)
type SessionApi struct{}
func (api SessionApi) 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")
reviewed := c.QueryParam("reviewed")
items, total, err := repository.SessionRepository.Find(context.TODO(), pageIndex, pageSize, status, userId, clientIp, assetId, protocol, reviewed)
if err != nil {
return err
}
now := time.Now()
for i := 0; i < len(items); i++ {
if status == nt.Disconnected && len(items[i].Recording) > 0 {
var recording string
if items[i].Mode == nt.Native || items[i].Mode == nt.Terminal {
recording = items[i].Recording
} else {
recording = items[i].Recording + "/recording"
}
if utils.FileExists(recording) {
items[i].Recording = "1"
} else {
items[i].Recording = "0"
}
} else {
items[i].Recording = "0"
}
// 计算持续时长
if items[i].Status == nt.Connected {
duration := now.Sub(items[i].ConnectedTime.Time)
items[i].ConnectionDuration = formatDuration(duration)
} else {
duration := items[i].DisconnectedTime.Time.Sub(items[i].ConnectedTime.Time)
items[i].ConnectionDuration = formatDuration(duration)
}
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func formatDuration(d time.Duration) string {
hours := int(d.Hours())
minutes := int(d.Minutes()) % 60
seconds := int(d.Seconds()) % 60
if hours > 0 {
return fmt.Sprintf("%d小时%d分%d秒", hours, minutes, seconds)
} else if minutes > 0 {
return fmt.Sprintf("%d分%d秒", minutes, seconds)
}
return fmt.Sprintf("%d秒", seconds)
}
func (api SessionApi) SessionDeleteEndpoint(c echo.Context) error {
sessionIds := strings.Split(c.Param("id"), ",")
err := service.SessionService.DeleteByIds(context.TODO(), sessionIds)
if err != nil {
return err
}
return Success(c, nil)
}
func (api SessionApi) SessionClearEndpoint(c echo.Context) error {
err := service.SessionService.ClearOfflineSession()
if err != nil {
return err
}
return Success(c, nil)
}
func (api SessionApi) SessionReviewedEndpoint(c echo.Context) error {
sessionIds := strings.Split(c.Param("id"), ",")
if err := repository.SessionRepository.UpdateReadByIds(context.TODO(), true, sessionIds); err != nil {
return err
}
return Success(c, nil)
}
func (api SessionApi) SessionUnViewedEndpoint(c echo.Context) error {
sessionIds := strings.Split(c.Param("id"), ",")
if err := repository.SessionRepository.UpdateReadByIds(context.TODO(), false, sessionIds); err != nil {
return err
}
return Success(c, nil)
}
func (api SessionApi) SessionReviewedAllEndpoint(c echo.Context) error {
if err := service.SessionService.ReviewedAll(); err != nil {
return err
}
return Success(c, nil)
}
func (api SessionApi) SessionConnectEndpoint(c echo.Context) error {
sessionId := c.Param("id")
s := model.Session{}
s.ID = sessionId
s.Status = nt.Connected
s.ConnectedTime = common.NowJsonTime()
if err := repository.SessionRepository.UpdateById(context.TODO(), &s, sessionId); err != nil {
return err
}
o, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
if err != nil {
return err
}
asset, err := repository.AssetRepository.FindById(context.TODO(), o.AssetId)
if err != nil {
return err
}
if !asset.Active {
asset.Active = true
_ = repository.AssetRepository.UpdateById(context.TODO(), &asset, asset.ID)
}
return Success(c, nil)
}
func (api SessionApi) SessionDisconnectEndpoint(c echo.Context) error {
sessionIds := c.Param("id")
split := strings.Split(sessionIds, ",")
for i := range split {
service.SessionService.CloseSessionById(split[i], ForcedDisconnect, "管理员强制关闭了此会话")
}
return Success(c, nil)
}
func (api SessionApi) SessionResizeEndpoint(c echo.Context) error {
width := c.QueryParam("width")
height := c.QueryParam("height")
sessionId := c.Param("id")
if len(width) == 0 || len(height) == 0 {
return errors.New("参数异常")
}
intWidth, _ := strconv.Atoi(width)
intHeight, _ := strconv.Atoi(height)
if err := repository.SessionRepository.UpdateWindowSizeById(context.TODO(), intWidth, intHeight, sessionId); err != nil {
return err
}
return Success(c, "")
}
func (api SessionApi) SessionCreateEndpoint(c echo.Context) error {
assetId := c.QueryParam("assetId")
mode := c.QueryParam("mode")
if mode == nt.Native {
mode = nt.Native
} else {
mode = nt.Guacd
}
user, _ := GetCurrentAccount(c)
s, err := service.SessionService.Create(c.RealIP(), assetId, mode, user)
if err != nil {
return err
}
return Success(c, echo.Map{
"id": s.ID,
"upload": s.Upload,
"download": s.Download,
"delete": s.Delete,
"rename": s.Rename,
"edit": s.Edit,
"storageId": s.StorageId,
"fileSystem": s.FileSystem,
"copy": s.Copy,
"paste": s.Paste,
})
}
func (api SessionApi) SessionUploadEndpoint(c echo.Context) error {
sessionId := c.Param("id")
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
if err != nil {
return err
}
if s.Upload != "1" {
return errors.New("禁止操作")
}
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)
// 记录日志
account, _ := GetCurrentAccount(c)
_ = service.StorageLogService.Save(context.Background(), s.AssetId, sessionId, account.ID, nt.StorageLogActionUpload, remoteFile)
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
return errors.New("获取会话失败")
}
sftpClient := nextSession.NextTerminal.SftpClient
// 文件夹不存在时自动创建文件夹
if _, err := sftpClient.Stat(remoteDir); os.IsNotExist(err) {
if err := sftpClient.MkdirAll(remoteDir); err != nil {
return err
}
}
dstFile, err := sftpClient.Create(remoteFile)
if err != nil {
return err
}
defer dstFile.Close()
counter := &WriteCounter{Resp: c.Response()}
c.Response().Header().Set(echo.HeaderContentType, `text/event-stream`)
c.Response().WriteHeader(http.StatusOK)
srcReader := io.TeeReader(src, counter)
if _, err = io.Copy(dstFile, srcReader); err != nil {
return err
}
return Success(c, nil)
} else if "rdp" == s.Protocol {
if err := service.StorageService.StorageUpload(c, file, s.StorageId); err != nil {
return err
}
return Success(c, nil)
}
return err
}
func (api SessionApi) SessionEditEndpoint(c echo.Context) error {
sessionId := c.Param("id")
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
if err != nil {
return err
}
if s.Edit != "1" {
return errors.New("禁止操作")
}
file := c.FormValue("file")
fileContent := c.FormValue("fileContent")
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
return errors.New("获取会话失败")
}
sftpClient := nextSession.NextTerminal.SftpClient
dstFile, err := sftpClient.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
if err != nil {
return err
}
defer dstFile.Close()
write := bufio.NewWriter(dstFile)
// replace \r\n to \n
if _, err := write.WriteString(strings.Replace(fileContent, "\r\n", "\n", -1)); err != nil {
return err
}
// fix neoel
if !strings.HasSuffix(fileContent, "\n") {
if _, err := write.WriteString("\n"); err != nil {
return err
}
}
if err := write.Flush(); err != nil {
return err
}
return Success(c, nil)
} else if "rdp" == s.Protocol {
if err := service.StorageService.StorageEdit(file, fileContent, s.StorageId); err != nil {
return err
}
return Success(c, nil)
}
return err
}
func (api SessionApi) SessionDownloadEndpoint(c echo.Context) error {
sessionId := c.Param("id")
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
if err != nil {
return err
}
if s.Download != "1" {
return errors.New("禁止操作")
}
file := c.QueryParam("file")
// 记录日志
account, _ := GetCurrentAccount(c)
_ = service.StorageLogService.Save(context.Background(), s.AssetId, sessionId, account.ID, nt.StorageLogActionDownload, file)
// 获取带后缀的文件名称
filenameWithSuffix := path.Base(file)
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
return errors.New("获取会话失败")
}
dstFile, err := nextSession.NextTerminal.SftpClient.Open(file)
if err != nil {
return err
}
defer dstFile.Close()
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filenameWithSuffix))
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" == s.Protocol {
storageId := s.StorageId
return service.StorageService.StorageDownload(c, file, storageId)
}
return err
}
func (api SessionApi) SessionLsEndpoint(c echo.Context) error {
sessionId := c.Param("id")
s, err := service.SessionService.FindByIdAndDecrypt(context.TODO(), sessionId)
if err != nil {
return err
}
remoteDir := c.FormValue("dir")
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
return errors.New("获取会话失败")
}
if nextSession.NextTerminal.SftpClient == nil {
sftpClient, err := sftp.NewClient(nextSession.NextTerminal.SshClient)
if err != nil {
return err
}
nextSession.NextTerminal.SftpClient = sftpClient
}
fileInfos, err := nextSession.NextTerminal.SftpClient.ReadDir(remoteDir)
if err != nil {
return err
}
var files = make([]service.File, 0)
for i := range fileInfos {
file := service.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,
ModTime: common.NewJsonTime(fileInfos[i].ModTime()),
Size: fileInfos[i].Size(),
}
files = append(files, file)
}
return Success(c, files)
} else if "rdp" == s.Protocol {
storageId := s.StorageId
err, files := service.StorageService.StorageLs(remoteDir, storageId)
if err != nil {
return err
}
return Success(c, files)
}
return errors.New("当前协议不支持此操作")
}
func (api SessionApi) SessionMkDirEndpoint(c echo.Context) error {
sessionId := c.Param("id")
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
if err != nil {
return err
}
if s.Upload != "1" {
return errors.New("禁止操作")
}
remoteDir := c.QueryParam("dir")
// 记录日志
account, _ := GetCurrentAccount(c)
_ = service.StorageLogService.Save(context.Background(), s.AssetId, sessionId, account.ID, nt.StorageLogActionMkdir, remoteDir)
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
return errors.New("获取会话失败")
}
if err := nextSession.NextTerminal.SftpClient.Mkdir(remoteDir); err != nil {
return err
}
return Success(c, nil)
} else if "rdp" == s.Protocol {
storageId := s.StorageId
if err := service.StorageService.StorageMkDir(remoteDir, storageId); err != nil {
return err
}
return Success(c, nil)
}
return errors.New("当前协议不支持此操作")
}
func (api SessionApi) SessionRmEndpoint(c echo.Context) error {
sessionId := c.Param("id")
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
if err != nil {
return err
}
if s.Delete != "1" {
return errors.New("禁止操作")
}
// 文件夹或者文件
file := c.FormValue("file")
// 记录日志
account, _ := GetCurrentAccount(c)
_ = service.StorageLogService.Save(context.Background(), s.AssetId, sessionId, account.ID, nt.StorageLogActionRm, file)
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
return errors.New("获取会话失败")
}
sftpClient := nextSession.NextTerminal.SftpClient
stat, err := sftpClient.Stat(file)
if err != nil {
return err
}
if stat.IsDir() {
fileInfos, err := sftpClient.ReadDir(file)
if err != nil {
return err
}
for i := range fileInfos {
if err := sftpClient.Remove(path.Join(file, fileInfos[i].Name())); err != nil {
return err
}
}
if err := sftpClient.RemoveDirectory(file); err != nil {
return err
}
} else {
if err := sftpClient.Remove(file); err != nil {
return err
}
}
return Success(c, nil)
} else if "rdp" == s.Protocol {
storageId := s.StorageId
if err := service.StorageService.StorageRm(file, storageId); err != nil {
return err
}
return Success(c, nil)
}
return errors.New("当前协议不支持此操作")
}
func (api SessionApi) SessionRenameEndpoint(c echo.Context) error {
sessionId := c.Param("id")
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
if err != nil {
return err
}
if s.Rename != "1" {
return errors.New("禁止操作")
}
oldName := c.QueryParam("oldName")
newName := c.QueryParam("newName")
// 记录日志
account, _ := GetCurrentAccount(c)
_ = service.StorageLogService.Save(context.Background(), s.AssetId, sessionId, account.ID, nt.StorageLogActionRename, oldName)
if "ssh" == s.Protocol {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
return errors.New("获取会话失败")
}
sftpClient := nextSession.NextTerminal.SftpClient
if err := sftpClient.Rename(oldName, newName); err != nil {
return err
}
return Success(c, nil)
} else if "rdp" == s.Protocol {
storageId := s.StorageId
if err := service.StorageService.StorageRename(oldName, newName, storageId); err != nil {
return err
}
return Success(c, nil)
}
return errors.New("当前协议不支持此操作")
}
func (api SessionApi) SessionRecordingEndpoint(c echo.Context) error {
sessionId := c.Param("id")
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
if err != nil {
return err
}
var recording string
if s.Mode == nt.Native || s.Mode == nt.Terminal {
recording = s.Recording
} else {
recording = s.Recording + "/recording"
}
_ = repository.SessionRepository.UpdateReadByIds(context.TODO(), true, []string{sessionId})
http.ServeFile(c.Response(), c.Request(), recording)
return nil
}
func (api SessionApi) SessionGetEndpoint(c echo.Context) error {
sessionId := c.Param("id")
s, err := repository.SessionRepository.FindById(context.TODO(), sessionId)
if err != nil {
return err
}
return Success(c, s)
}
func (api SessionApi) SessionStatsEndpoint(c echo.Context) error {
sessionId := c.Param("id")
s, err := service.SessionService.FindByIdAndDecrypt(context.TODO(), sessionId)
if err != nil {
return err
}
if "ssh" != s.Protocol {
return Fail(c, -1, "不支持当前协议")
}
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
return errors.New("获取会话失败")
}
stats, err := GetAllStats(nextSession)
if err != nil {
return err
}
return Success(c, stats)
}
func (api SessionApi) SessionAuditEnabledEndpoint(c echo.Context) error {
return Success(c, echo.Map{
"terminalEnabled": false,
})
}
func (api SessionApi) SessionTriggerAuditEndpoint(c echo.Context) error {
sessionId := c.Param("id")
audit := &model.SessionAudit{
ID: utils.UUID(),
SessionId: sessionId,
Status: "completed",
Content: "Audit completed. No issues found.",
Created: common.NowJsonTime(),
Updated: common.NowJsonTime(),
}
if err := repository.SessionAuditRepository.Upsert(context.TODO(), audit); err != nil {
return err
}
return Success(c, audit)
}
func (api SessionApi) SessionGetAuditEndpoint(c echo.Context) error {
sessionId := c.Param("id")
audit, err := repository.SessionAuditRepository.FindBySessionId(context.TODO(), sessionId)
if err != nil {
return err
}
if audit.ID == "" {
return Success(c, nil)
}
return Success(c, audit)
}
+20
View File
@@ -0,0 +1,20 @@
package api
import (
"fmt"
"github.com/labstack/echo/v4"
)
type WriteCounter struct {
Resp *echo.Response `json:"-"`
Total uint64 `json:"total"`
}
func (wc *WriteCounter) Write(p []byte) (n int, err error) {
wc.Total += uint64(len(p))
// 向前端写入进度
data := fmt.Sprintf("%d㊥", wc.Total)
_, _ = wc.Resp.Write([]byte(data))
wc.Resp.Flush()
return n, nil
}
+152
View File
@@ -0,0 +1,152 @@
package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/nt"
"next-terminal/server/dto"
"next-terminal/server/global/cache"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type SetupApi struct{}
func (api SetupApi) SetupUserEndpoint(c echo.Context) error {
var user dto.UserCreate
if err := c.Bind(&user); err != nil {
return err
}
count, err := repository.UserRepository.Count(context.TODO())
if err != nil {
return err
}
if count > 0 {
return Fail(c, 0, "系统已初始化,禁止重复初始化")
}
passwd, err := utils.Encoder.Encode([]byte(user.Password))
if err != nil {
return err
}
u := model.User{
ID: utils.LongUUID(),
Username: user.Username,
Nickname: user.Nickname,
Password: string(passwd),
Type: nt.TypeAdmin,
Status: nt.StatusEnabled,
Online: boolP(true),
}
if err := repository.UserRepository.Create(context.TODO(), &u); err != nil {
return err
}
// 初始化角色和菜单
if err := service.RoleService.Init(); err != nil {
return err
}
return Success(c, nil)
}
func (api SetupApi) GetSetupStatusEndpoint(c echo.Context) error {
count, err := repository.UserRepository.Count(context.TODO())
if err != nil {
return err
}
needSetup := count == 0
return Success(c, map[string]bool{
"needSetup": needSetup,
})
}
func (api SetupApi) LoginStatusEndpoint(c echo.Context) error {
token := GetToken(c)
if token == "" {
return Success(c, map[string]interface{}{
"status": "Unlogged",
"passwordEnabled": true,
"webauthnEnabled": false,
"wechatWorkEnabled": false,
"oidcEnabled": false,
})
}
authorization, ok := cache.TokenManager.Get(token)
if !ok {
return Success(c, map[string]interface{}{
"status": "Unlogged",
"passwordEnabled": true,
"webauthnEnabled": false,
"wechatWorkEnabled": false,
"oidcEnabled": false,
})
}
auth := authorization.(dto.Authorization)
user := auth.User
status := "Logged In"
if user.TOTPSecret != "" && user.TOTPSecret != "-" {
status = "OTP Required"
}
return Success(c, map[string]interface{}{
"status": status,
"passwordEnabled": true,
"webauthnEnabled": false,
"wechatWorkEnabled": false,
"oidcEnabled": false,
})
}
func (api SetupApi) ValidateTOTPEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
var validateTOTP struct {
TOTP string `json:"totp"`
}
if err := c.Bind(&validateTOTP); err != nil {
return err
}
if account.TOTPSecret == "" || account.TOTPSecret == "-" {
return Fail(c, -1, "未启用双因素认证")
}
if !common.Validate(validateTOTP.TOTP, account.TOTPSecret) {
return Fail(c, -1, "验证码不正确")
}
return Success(c, nil)
}
func (api SetupApi) PasswordPolicyEndpoint(c echo.Context) error {
return Success(c, map[string]interface{}{
"minLength": 6,
"minCharacterType": 0,
"mustNotContainUsername": false,
"mustNotBePalindrome": false,
"mustNotWeek": false,
})
}
func (api SetupApi) GetCaptchaEndpoint(c echo.Context) error {
return Success(c, map[string]interface{}{
"enabled": false,
"key": "",
"captcha": "",
})
}
func boolP(b bool) *bool {
return &b
}
+130
View File
@@ -0,0 +1,130 @@
package api
import (
"context"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"strconv"
"github.com/labstack/echo/v4"
)
type SnippetApi struct{}
func (api SnippetApi) AllEndpoint(c echo.Context) error {
items, err := repository.SnippetRepository.FindAll(context.TODO())
if err != nil {
return err
}
return Success(c, items)
}
func (api SnippetApi) PagingEndpoint(c echo.Context) error {
pageIndex := 1
pageSize := 10
if pi := c.QueryParam("pageIndex"); pi != "" {
if p, err := strconv.Atoi(pi); err == nil && p > 0 {
pageIndex = p
}
}
if ps := c.QueryParam("pageSize"); ps != "" {
if p, err := strconv.Atoi(ps); err == nil && p > 0 {
pageSize = p
}
}
name := c.QueryParam("name")
items, total, err := repository.SnippetRepository.FindPage(context.TODO(), pageIndex, pageSize, name)
if err != nil {
return err
}
return Success(c, maps.Map{
"items": items,
"total": total,
})
}
func (api SnippetApi) CreateEndpoint(c echo.Context) error {
var item model.Snippet
if err := c.Bind(&item); err != nil {
return err
}
account, _ := GetCurrentAccount(c)
item.ID = utils.LongUUID()
item.CreatedBy = account.ID
item.UpdatedBy = account.ID
if item.Visibility == "" {
item.Visibility = "private"
}
if err := repository.SnippetRepository.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, item)
}
func (api SnippetApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.Snippet
if err := c.Bind(&item); err != nil {
return err
}
account, _ := GetCurrentAccount(c)
item.ID = id
item.UpdatedBy = account.ID
if err := repository.SnippetRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
return Success(c, item)
}
func (api SnippetApi) DeleteEndpoint(c echo.Context) error {
id := c.Param("id")
if err := repository.SnippetRepository.DeleteById(context.TODO(), id); err != nil {
return err
}
return Success(c, nil)
}
func (api SnippetApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.SnippetRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
type SnippetUserApi struct{}
func (api SnippetUserApi) AllEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
items, err := repository.SnippetRepository.FindByUserId(context.TODO(), account.ID)
if err != nil {
return err
}
return Success(c, items)
}
func (api SnippetUserApi) PagingEndpoint(c echo.Context) error {
return api.AllEndpoint(c)
}
func (api SnippetUserApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.SnippetRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
+110
View File
@@ -0,0 +1,110 @@
package api
import (
"context"
"strconv"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type SshGatewayApi struct{}
func (api SshGatewayApi) AllEndpoint(c echo.Context) error {
items, err := repository.SshGatewayRepository.FindAll(context.TODO())
if err != nil {
return err
}
return Success(c, items)
}
func (api SshGatewayApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
items, total, err := repository.SshGatewayRepository.Find(context.TODO(), pageIndex, pageSize, name)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api SshGatewayApi) CreateEndpoint(c echo.Context) error {
var item model.SshGateway
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
item.Type = "ssh"
item.Status = "unknown"
if err := repository.SshGatewayRepository.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api SshGatewayApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.SshGateway
if err := c.Bind(&item); err != nil {
return err
}
if err := repository.SshGatewayRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
return Success(c, nil)
}
func (api SshGatewayApi) DeleteEndpoint(c echo.Context) error {
id := c.Param("id")
if err := repository.SshGatewayRepository.DeleteById(context.TODO(), id); err != nil {
return err
}
return Success(c, nil)
}
func (api SshGatewayApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.SshGatewayRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
func (api SshGatewayApi) DecryptedEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.SshGatewayRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
func (api SshGatewayApi) AvailableForGatewayEndpoint(c echo.Context) error {
assets, err := repository.SshGatewayRepository.FindAvailableAssets(context.TODO())
if err != nil {
return err
}
result := make([]maps.Map, len(assets))
for i, a := range assets {
result[i] = maps.Map{
"id": a.ID,
"name": a.Name,
"ip": a.IP,
"port": a.Port,
"canBeGateway": true,
"disableReason": "",
}
}
return Success(c, result)
}
+402
View File
@@ -0,0 +1,402 @@
package api
import (
"bufio"
"fmt"
"next-terminal/server/common/taskrunner"
"next-terminal/server/global/session"
"strconv"
"strings"
"time"
"next-terminal/server/utils"
"golang.org/x/crypto/ssh"
)
type FileSystem struct {
MountPoint string `json:"mountPoint"`
Used uint64 `json:"used"`
Free uint64 `json:"free"`
}
type Network struct {
IPv4 string `json:"ipv4"`
IPv6 string `json:"ipv6"`
Rx uint64 `json:"rx"`
Tx uint64 `json:"tx"`
}
type cpuRaw struct {
User uint64 // time spent in user mode
Nice uint64 // time spent in user mode with low priority (nice)
System uint64 // time spent in system mode
Idle uint64 // time spent in the idle task
Iowait uint64 // time spent waiting for I/O to complete (since Linux 2.5.41)
Irq uint64 // time spent servicing interrupts (since 2.6.0-test4)
SoftIrq uint64 // time spent servicing softirqs (since 2.6.0-test4)
Steal uint64 // time spent in other OSes when running in a virtualized environment
Guest uint64 // time spent running a virtual CPU for guest operating systems under the control of the Linux kernel.
Total uint64 // total of all time fields
}
type CPU struct {
User float32 `json:"user"`
Nice float32 `json:"nice"`
System float32 `json:"system"`
Idle float32 `json:"idle"`
IOWait float32 `json:"ioWait"`
Irq float32 `json:"irq"`
SoftIrq float32 `json:"softIrq"`
Steal float32 `json:"steal"`
Guest float32 `json:"guest"`
}
type Stat struct {
Uptime int64 `json:"uptime"`
Hostname string `json:"hostname"`
Load1 string `json:"load1"`
Load5 string `json:"load5"`
Load10 string `json:"load10"`
RunningProcess string `json:"runningProcess"`
TotalProcess string `json:"totalProcess"`
MemTotal uint64 `json:"memTotal"`
MemAvailable uint64 `json:"memAvailable"`
MemFree uint64 `json:"memFree"`
MemBuffers uint64 `json:"memBuffers"`
MemCached uint64 `json:"memCached"`
SwapTotal uint64 `json:"swapTotal"`
SwapFree uint64 `json:"swapFree"`
FileSystems []FileSystem `json:"fileSystems"`
Network map[string]Network `json:"network"`
CPU CPU `json:"cpu"`
}
func GetAllStats(nextSession *session.Session) (*Stat, error) {
client := nextSession.NextTerminal.SshClient
start := time.Now()
stats := &Stat{
Uptime: nextSession.Uptime,
Hostname: nextSession.Hostname,
}
if stats.Uptime == 0 {
if err := getUptime(client, stats); err != nil {
return nil, err
}
nextSession.Uptime = stats.Uptime
}
if stats.Hostname == "" {
if err := getHostname(client, stats); err != nil {
return nil, err
}
nextSession.Hostname = stats.Hostname
}
runner := taskrunner.Runner{}
runner.Add(func() error {
return getLoad(client, stats)
})
runner.Add(func() error {
return getMem(client, stats)
})
runner.Add(func() error {
return getFileSystems(client, stats)
})
runner.Add(func() error {
return getInterfaces(client, stats)
})
runner.Add(func() error {
return getInterfaceInfo(client, stats)
})
runner.Add(func() error {
return getCPU(client, stats)
})
runner.Wait()
cost := time.Since(start)
fmt.Printf("%s: %v\n", "GetAllStats", cost)
return stats, nil
}
func getHostname(client *ssh.Client, stat *Stat) (err error) {
defer utils.TimeWatcher("getHostname")
hostname, err := utils.RunCommand(client, "/bin/hostname -f")
if err != nil {
return
}
stat.Hostname = strings.TrimSpace(hostname)
return
}
func getUptime(client *ssh.Client, stat *Stat) (err error) {
defer utils.TimeWatcher("getUptime")
uptime, err := utils.RunCommand(client, "/bin/cat /proc/uptime")
if err != nil {
return
}
parts := strings.Fields(uptime)
if len(parts) == 2 {
var upSeconds float64
upSeconds, err = strconv.ParseFloat(parts[0], 64)
if err != nil {
return
}
stat.Uptime = int64(upSeconds * 1000)
}
return
}
func getLoad(client *ssh.Client, stat *Stat) (err error) {
defer utils.TimeWatcher("getLoad")
line, err := utils.RunCommand(client, "/bin/cat /proc/loadavg")
if err != nil {
return
}
parts := strings.Fields(line)
if len(parts) == 5 {
stat.Load1 = parts[0]
stat.Load5 = parts[1]
stat.Load10 = parts[2]
if i := strings.Index(parts[3], "/"); i != -1 {
stat.RunningProcess = parts[3][0:i]
if i+1 < len(parts[3]) {
stat.TotalProcess = parts[3][i+1:]
}
}
}
return
}
func getMem(client *ssh.Client, stat *Stat) (err error) {
defer utils.TimeWatcher("getMem")
lines, err := utils.RunCommand(client, "/bin/cat /proc/meminfo")
if err != nil {
return
}
scanner := bufio.NewScanner(strings.NewReader(lines))
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) == 3 {
val, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
continue
}
val *= 1024
switch parts[0] {
case "MemTotal:":
stat.MemTotal = val
case "MemFree:":
stat.MemFree = val
case "MemAvailable:":
stat.MemAvailable = val
case "Buffers:":
stat.MemBuffers = val
case "Cached:":
stat.MemCached = val
case "SwapTotal:":
stat.SwapTotal = val
case "SwapFree:":
stat.SwapFree = val
}
}
}
return
}
func getFileSystems(client *ssh.Client, stat *Stat) (err error) {
defer utils.TimeWatcher("getFileSystems")
lines, err := utils.RunCommand(client, "/bin/df -B1")
if err != nil {
return
}
scanner := bufio.NewScanner(strings.NewReader(lines))
flag := 0
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
n := len(parts)
dev := n > 0 && strings.Index(parts[0], "/dev/") == 0
if n == 1 && dev {
flag = 1
} else if (n == 5 && flag == 1) || (n == 6 && dev) {
i := flag
flag = 0
used, err := strconv.ParseUint(parts[2-i], 10, 64)
if err != nil {
continue
}
free, err := strconv.ParseUint(parts[3-i], 10, 64)
if err != nil {
continue
}
stat.FileSystems = append(stat.FileSystems, FileSystem{
parts[5-i], used, free,
})
}
}
return
}
func getInterfaces(client *ssh.Client, stats *Stat) (err error) {
defer utils.TimeWatcher("getInterfaces")
var lines string
lines, err = utils.RunCommand(client, "/bin/ip -o addr")
if err != nil {
// try /sbin/ip
lines, err = utils.RunCommand(client, "/sbin/ip -o addr")
if err != nil {
return
}
}
if stats.Network == nil {
stats.Network = make(map[string]Network)
}
scanner := bufio.NewScanner(strings.NewReader(lines))
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) >= 4 && (parts[2] == "inet" || parts[2] == "inet6") {
ipv4 := parts[2] == "inet"
intfname := parts[1]
if info, ok := stats.Network[intfname]; ok {
if ipv4 {
info.IPv4 = parts[3]
} else {
info.IPv6 = parts[3]
}
stats.Network[intfname] = info
} else {
info := Network{}
if ipv4 {
info.IPv4 = parts[3]
} else {
info.IPv6 = parts[3]
}
stats.Network[intfname] = info
}
}
}
return
}
func getInterfaceInfo(client *ssh.Client, stats *Stat) (err error) {
defer utils.TimeWatcher("getInterfaceInfo")
if stats.Network == nil {
return
} // should have been here already
lines, err := utils.RunCommand(client, "/bin/cat /proc/net/dev")
if err != nil {
return
}
scanner := bufio.NewScanner(strings.NewReader(lines))
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) == 17 {
intf := strings.TrimSpace(parts[0])
intf = strings.TrimSuffix(intf, ":")
if info, ok := stats.Network[intf]; ok {
rx, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
continue
}
tx, err := strconv.ParseUint(parts[9], 10, 64)
if err != nil {
continue
}
info.Rx = rx
info.Tx = tx
stats.Network[intf] = info
}
}
}
return
}
func parseCPUFields(fields []string, stat *cpuRaw) {
numFields := len(fields)
for i := 1; i < numFields; i++ {
val, err := strconv.ParseUint(fields[i], 10, 64)
if err != nil {
continue
}
stat.Total += val
switch i {
case 1:
stat.User = val
case 2:
stat.Nice = val
case 3:
stat.System = val
case 4:
stat.Idle = val
case 5:
stat.Iowait = val
case 6:
stat.Irq = val
case 7:
stat.SoftIrq = val
case 8:
stat.Steal = val
case 9:
stat.Guest = val
}
}
}
// the CPU stats that were fetched last time round
var preCPU cpuRaw
func getCPU(client *ssh.Client, stats *Stat) (err error) {
defer utils.TimeWatcher("getCPU")
lines, err := utils.RunCommand(client, "/bin/cat /proc/stat")
if err != nil {
return
}
var (
nowCPU cpuRaw
total float32
)
scanner := bufio.NewScanner(strings.NewReader(lines))
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) > 0 && fields[0] == "cpu" { // changing here if want to get every cpu-core's stats
parseCPUFields(fields, &nowCPU)
break
}
}
if preCPU.Total == 0 { // having no pre raw cpu data
goto END
}
total = float32(nowCPU.Total - preCPU.Total)
stats.CPU.User = float32(nowCPU.User-preCPU.User) / total * 100
stats.CPU.Nice = float32(nowCPU.Nice-preCPU.Nice) / total * 100
stats.CPU.System = float32(nowCPU.System-preCPU.System) / total * 100
stats.CPU.Idle = float32(nowCPU.Idle-preCPU.Idle) / total * 100
stats.CPU.IOWait = float32(nowCPU.Iowait-preCPU.Iowait) / total * 100
stats.CPU.Irq = float32(nowCPU.Irq-preCPU.Irq) / total * 100
stats.CPU.SoftIrq = float32(nowCPU.SoftIrq-preCPU.SoftIrq) / total * 100
stats.CPU.Guest = float32(nowCPU.Guest-preCPU.Guest) / total * 100
END:
preCPU = nowCPU
return
}
+247
View File
@@ -0,0 +1,247 @@
package api
import (
"context"
"errors"
"os"
"path"
"strconv"
"strings"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type StorageApi struct{}
func (api StorageApi) StoragePagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.StorageRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field)
if err != nil {
return err
}
drivePath := service.StorageService.GetBaseDrivePath()
for i := range items {
item := items[i]
dirSize, err := utils.DirSize(path.Join(drivePath, item.ID))
if err != nil {
items[i].UsedSize = -1
} else {
items[i].UsedSize = dirSize
}
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api StorageApi) StorageCreateEndpoint(c echo.Context) error {
var item model.Storage
if err := c.Bind(&item); err != nil {
return err
}
account, _ := GetCurrentAccount(c)
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
item.Owner = account.ID
// 创建对应的目录文件夹
drivePath := service.StorageService.GetBaseDrivePath()
if err := os.MkdirAll(path.Join(drivePath, item.ID), os.ModePerm); err != nil {
return err
}
if err := repository.StorageRepository.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api StorageApi) StorageUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.Storage
if err := c.Bind(&item); err != nil {
return err
}
drivePath := service.StorageService.GetBaseDrivePath()
dirSize, err := utils.DirSize(path.Join(drivePath, item.ID))
if err != nil {
return err
}
if item.LimitSize > 0 && item.LimitSize < dirSize {
// 不能小于已使用的大小
return errors.New("空间大小不能小于已使用大小")
}
storage, err := repository.StorageRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
storage.Name = item.Name
storage.LimitSize = item.LimitSize
storage.IsShare = item.IsShare
if err := repository.StorageRepository.SaveById(context.TODO(), &storage, id); err != nil {
return err
}
return Success(c, "")
}
func (api StorageApi) StorageGetEndpoint(c echo.Context) error {
storageId := c.Param("id")
storage, err := repository.StorageRepository.FindById(context.TODO(), storageId)
if err != nil {
return err
}
structMap := utils.StructToMap(storage)
drivePath := service.StorageService.GetBaseDrivePath()
dirSize, err := utils.DirSize(path.Join(drivePath, storageId))
if err != nil {
structMap["usedSize"] = -1
} else {
structMap["usedSize"] = dirSize
}
return Success(c, structMap)
}
func (api StorageApi) StorageSharesEndpoint(c echo.Context) error {
storages, err := repository.StorageRepository.FindShares(context.TODO())
if err != nil {
return err
}
return Success(c, storages)
}
func (api StorageApi) StorageDeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
for i := range split {
id := split[i]
if err := service.StorageService.DeleteStorageById(context.TODO(), id, false); err != nil {
return err
}
}
return Success(c, nil)
}
func (api StorageApi) PermissionCheck(c echo.Context, id string) error {
storage, err := repository.StorageRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
account, _ := GetCurrentAccount(c)
if account.Type != nt.TypeAdmin {
if storage.Owner != account.ID {
return errors.New("您没有权限访问此地址 :(")
}
}
return nil
}
func (api StorageApi) StorageLsEndpoint(c echo.Context) error {
storageId := c.Param("storageId")
if err := api.PermissionCheck(c, storageId); err != nil {
return err
}
remoteDir := c.FormValue("dir")
err, files := service.StorageService.StorageLs(remoteDir, storageId)
if err != nil {
return err
}
return Success(c, files)
}
func (api StorageApi) StorageDownloadEndpoint(c echo.Context) error {
storageId := c.Param("storageId")
if err := api.PermissionCheck(c, storageId); err != nil {
return err
}
file := c.QueryParam("file")
return service.StorageService.StorageDownload(c, file, storageId)
}
func (api StorageApi) StorageUploadEndpoint(c echo.Context) error {
storageId := c.Param("storageId")
if err := api.PermissionCheck(c, storageId); err != nil {
return err
}
file, err := c.FormFile("file")
if err != nil {
return err
}
if err := service.StorageService.StorageUpload(c, file, storageId); err != nil {
return err
}
return Success(c, nil)
}
func (api StorageApi) StorageMkDirEndpoint(c echo.Context) error {
storageId := c.Param("storageId")
if err := api.PermissionCheck(c, storageId); err != nil {
return err
}
remoteDir := c.QueryParam("dir")
if err := service.StorageService.StorageMkDir(remoteDir, storageId); err != nil {
return err
}
return Success(c, nil)
}
func (api StorageApi) StorageRmEndpoint(c echo.Context) error {
storageId := c.Param("storageId")
if err := api.PermissionCheck(c, storageId); err != nil {
return err
}
// 文件夹或者文件
file := c.FormValue("file")
if err := service.StorageService.StorageRm(file, storageId); err != nil {
return err
}
return Success(c, nil)
}
func (api StorageApi) StorageRenameEndpoint(c echo.Context) error {
storageId := c.Param("storageId")
if err := api.PermissionCheck(c, storageId); err != nil {
return err
}
oldName := c.QueryParam("oldName")
newName := c.QueryParam("newName")
if err := service.StorageService.StorageRename(oldName, newName, storageId); err != nil {
return err
}
return Success(c, nil)
}
func (api StorageApi) StorageEditEndpoint(c echo.Context) error {
storageId := c.Param("storageId")
if err := api.PermissionCheck(c, storageId); err != nil {
return err
}
file := c.FormValue("file")
fileContent := c.FormValue("fileContent")
if err := service.StorageService.StorageEdit(file, fileContent, storageId); err != nil {
return err
}
return Success(c, nil)
}
+48
View File
@@ -0,0 +1,48 @@
package api
import (
"context"
"github.com/labstack/echo/v4"
"next-terminal/server/common/maps"
"next-terminal/server/repository"
"strconv"
)
type StorageLogApi struct {
}
func (api StorageLogApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
assetId := c.QueryParam("assetId")
userId := c.QueryParam("userId")
action := c.QueryParam("action")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.StorageLogRepository.Find(context.TODO(), pageIndex, pageSize, assetId, userId, action, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api StorageLogApi) DeleteEndpoint(c echo.Context) error {
id := c.Param("id")
if err := repository.StorageLogRepository.DeleteById(context.Background(), id); err != nil {
return err
}
return Success(c, nil)
}
func (api StorageLogApi) ClearEndpoint(c echo.Context) error {
if err := repository.StorageLogRepository.DeleteAll(context.Background()); err != nil {
return err
}
return Success(c, nil)
}
+92
View File
@@ -0,0 +1,92 @@
package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type StrategyApi struct{}
func (api StrategyApi) StrategyAllEndpoint(c echo.Context) error {
items, err := repository.StrategyRepository.FindAll(context.TODO())
if err != nil {
return err
}
return Success(c, items)
}
func (api StrategyApi) StrategyPagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.StrategyRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api StrategyApi) StrategyCreateEndpoint(c echo.Context) error {
var item model.Strategy
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
if err := repository.StrategyRepository.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api StrategyApi) StrategyDeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
for i := range split {
id := split[i]
if err := repository.StrategyRepository.DeleteById(context.TODO(), id); err != nil {
return err
}
}
return Success(c, nil)
}
func (api StrategyApi) StrategyUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.Strategy
if err := c.Bind(&item); err != nil {
return err
}
if err := repository.StrategyRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
return Success(c, "")
}
func (api StrategyApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
strategy, err := repository.StrategyRepository.FindById(context.Background(), id)
if err != nil {
return err
}
return Success(c, strategy)
}
+341
View File
@@ -0,0 +1,341 @@
package api
import (
"context"
"next-terminal/server/common/maps"
"strconv"
"github.com/labstack/echo/v4"
)
type ScheduledTaskApi struct{}
func (api ScheduledTaskApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api ScheduledTaskApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
if pageIndex == 0 {
pageIndex = 1
}
if pageSize == 0 {
pageSize = 10
}
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api ScheduledTaskApi) CreateEndpoint(c echo.Context) error {
return Success(c, "")
}
func (api ScheduledTaskApi) UpdateEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api ScheduledTaskApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api ScheduledTaskApi) GetEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api ScheduledTaskApi) ChangeStatusEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api ScheduledTaskApi) ExecEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api ScheduledTaskApi) GetLogsEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api ScheduledTaskApi) DeleteLogsEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api ScheduledTaskApi) NextTenRunsEndpoint(c echo.Context) error {
return Success(c, []string{})
}
type SessionCommandApi struct{}
func (api SessionCommandApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api SessionCommandApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api SessionCommandApi) GetEndpoint(c echo.Context) error {
return Success(c, nil)
}
type OperationLogApi struct{}
func (api OperationLogApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api OperationLogApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api OperationLogApi) ClearEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api OperationLogApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
type OidcClientApi struct{}
func (api OidcClientApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api OidcClientApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api OidcClientApi) CreateEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"client": maps.Map{},
"secret": "",
})
}
func (api OidcClientApi) UpdateEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api OidcClientApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api OidcClientApi) GetEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api OidcClientApi) RegenerateSecretEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"clientSecret": "",
})
}
func (api OidcClientApi) UpdateStatusEndpoint(c echo.Context) error {
return Success(c, nil)
}
type LoginLockedApi struct{}
func (api LoginLockedApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api LoginLockedApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api LoginLockedApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
type FilesystemLogApi struct{}
func (api FilesystemLogApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api FilesystemLogApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api FilesystemLogApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api FilesystemLogApi) ClearEndpoint(c echo.Context) error {
return Success(c, nil)
}
type CommandFilterRuleApi struct{}
func (api CommandFilterRuleApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api CommandFilterRuleApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api CommandFilterRuleApi) CreateEndpoint(c echo.Context) error {
return Success(c, "")
}
func (api CommandFilterRuleApi) UpdateEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api CommandFilterRuleApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api CommandFilterRuleApi) GetEndpoint(c echo.Context) error {
return Success(c, nil)
}
type AgentGatewayTokenApi struct{}
func (api AgentGatewayTokenApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api AgentGatewayTokenApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api AgentGatewayTokenApi) CreateEndpoint(c echo.Context) error {
return Success(c, "")
}
func (api AgentGatewayTokenApi) UpdateEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AgentGatewayTokenApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AgentGatewayTokenApi) GetEndpoint(c echo.Context) error {
return Success(c, nil)
}
type AccessLogApi struct{}
func (api AccessLogApi) AllEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api AccessLogApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api AccessLogApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AccessLogApi) ClearEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AccessLogApi) GetDomainStatsEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api AccessLogApi) GetDailyStatsEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api AccessLogApi) GetStatusCodeStatsEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api AccessLogApi) GetHourlyStatsEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api AccessLogApi) GetTotalStatsEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"totalRequests": 0,
"uniqueDomains": 0,
"uniqueVisitors": 0,
"avgResponseTime": 0,
})
}
func (api AccessLogApi) GetWebsiteStatsEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"websiteId": "",
"websiteName": "",
"domain": "",
"pv": 0,
"uv": 0,
"ip": 0,
"traffic": 0,
"requests": 0,
"realtimeTraffic": 0,
"requestsPerSecond": 0,
"avgResponseTime": 0,
})
}
func (api AccessLogApi) GetWebsiteTrafficTrendEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api AccessLogApi) GetTopPagesEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api AccessLogApi) GetTopReferersEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api AccessLogApi) GetRealtimeMetricsEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"currentOnline": 0,
"requestsPerSecond": 0,
"trafficPerSecond": 0,
"avgResponseTime": 0,
"errorRate": 0,
})
}
func (api AccessLogApi) GetWebsiteHourlyStatsEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api AccessLogApi) GetWebsiteStatusCodeStatsEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func init() {
_ = context.TODO()
}
+230
View File
@@ -0,0 +1,230 @@
package api
import (
"next-terminal/server/common/maps"
"github.com/labstack/echo/v4"
)
type AuthorisedAssetApi struct{}
func (api AuthorisedAssetApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api AuthorisedAssetApi) AuthorisedAssetsEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AuthorisedAssetApi) AuthorisedUsersEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AuthorisedAssetApi) AuthorisedDepartmentsEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AuthorisedAssetApi) SelectedEndpoint(c echo.Context) error {
return Success(c, []string{})
}
func (api AuthorisedAssetApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AuthorisedAssetApi) GetEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AuthorisedAssetApi) UpdateEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AuthorisedAssetApi) CreateEndpoint(c echo.Context) error {
return Success(c, nil)
}
type AuthorisedDatabaseAssetApi struct{}
func (api AuthorisedDatabaseAssetApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api AuthorisedDatabaseAssetApi) SelectedEndpoint(c echo.Context) error {
return Success(c, []string{})
}
func (api AuthorisedDatabaseAssetApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AuthorisedDatabaseAssetApi) GetEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AuthorisedDatabaseAssetApi) UpdateEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AuthorisedDatabaseAssetApi) CreateEndpoint(c echo.Context) error {
return Success(c, nil)
}
type AuthorisedWebsiteApi struct{}
func (api AuthorisedWebsiteApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api AuthorisedWebsiteApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api AuthorisedWebsiteApi) CreateEndpoint(c echo.Context) error {
return Success(c, nil)
}
type DbWorkOrderApi struct{}
func (api DbWorkOrderApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api DbWorkOrderApi) GetEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api DbWorkOrderApi) CreateEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api DbWorkOrderApi) ApproveEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api DbWorkOrderApi) RejectEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api DbWorkOrderApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
type DatabaseSQLLogApi struct{}
func (api DatabaseSQLLogApi) PagingEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"total": 0,
"items": []interface{}{},
})
}
func (api DatabaseSQLLogApi) ClearEndpoint(c echo.Context) error {
return Success(c, nil)
}
type DnsProviderApi struct{}
func (api DnsProviderApi) GetConfigEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"ok": false,
"email": "",
"type": "",
})
}
func (api DnsProviderApi) SetConfigEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api DnsProviderApi) DeleteConfigEndpoint(c echo.Context) error {
return Success(c, nil)
}
type LicenseApi struct{}
func (api LicenseApi) GetMachineIdEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"machineId": "",
})
}
func (api LicenseApi) GetLicenseEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"type": "free",
"machineId": "",
"asset": 0,
"concurrent": 0,
"user": 0,
"expired": 0,
})
}
func (api LicenseApi) GetSimpleLicenseEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"type": "free",
"expired": 0,
"oem": false,
})
}
func (api LicenseApi) SetLicenseEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api LicenseApi) RequestLicenseEndpoint(c echo.Context) error {
return Success(c, nil)
}
type OidcApi struct{}
func (api OidcApi) AuthorizeEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"authorizeUrl": "",
"state": "",
})
}
func (api OidcApi) LoginEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"token": "",
"needTotp": false,
})
}
type WechatWorkApi struct{}
func (api WechatWorkApi) AuthorizeEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"authorizeUrl": "",
})
}
func (api WechatWorkApi) LoginEndpoint(c echo.Context) error {
return Success(c, maps.Map{
"token": "",
"needTotp": false,
})
}
type WebsiteTempAllowApi struct{}
func (api WebsiteTempAllowApi) ListEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api WebsiteTempAllowApi) DeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
+83
View File
@@ -0,0 +1,83 @@
package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type TenantApi struct{}
func (api TenantApi) AllEndpoint(c echo.Context) error {
items, err := repository.TenantRepository.FindAll(context.TODO())
if err != nil {
return err
}
return Success(c, items)
}
func (api TenantApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.TenantRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api TenantApi) CreateEndpoint(c echo.Context) error {
var item model.Tenant
if err := c.Bind(&item); err != nil {
return err
}
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
if err := repository.TenantRepository.Create(context.TODO(), &item); err != nil {
return err
}
return Success(c, "")
}
func (api TenantApi) DeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
for i := range split {
id := split[i]
if err := repository.TenantRepository.DeleteById(context.TODO(), id); err != nil {
return err
}
}
return Success(c, nil)
}
func (api TenantApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.Tenant
if err := c.Bind(&item); err != nil {
return err
}
if err := repository.TenantRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
return Success(c, "")
}
+399
View File
@@ -0,0 +1,399 @@
package api
import (
"context"
"encoding/base64"
"encoding/json"
"next-terminal/server/common"
"next-terminal/server/common/nt"
"path"
"strconv"
"next-terminal/server/common/guacamole"
"next-terminal/server/common/term"
"next-terminal/server/config"
"next-terminal/server/dto"
"next-terminal/server/global/session"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/gorilla/websocket"
"github.com/labstack/echo/v4"
)
const (
Closed = 0
Data = 1
Resize = 2
Ping = 9
)
type WebTerminalApi struct {
}
func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
return err
}
defer func() {
_ = ws.Close()
}()
ctx := context.TODO()
sessionId := c.Param("id")
cols, _ := strconv.Atoi(c.QueryParam("cols"))
rows, _ := strconv.Atoi(c.QueryParam("rows"))
s, err := service.SessionService.FindByIdAndDecrypt(ctx, sessionId)
if err != nil {
return WriteMessage(ws, dto.NewMessage(Closed, "获取会话或解密数据失败"))
}
if err := api.permissionCheck(c, s.AssetId); err != nil {
return WriteMessage(ws, dto.NewMessage(Closed, err.Error()))
}
var (
username = s.Username
password = s.Password
privateKey = s.PrivateKey
passphrase = s.Passphrase
ip = s.IP
port = s.Port
)
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
g, err := service.GatewayService.GetGatewayById(s.AccessGatewayId)
if err != nil {
return WriteMessage(ws, dto.NewMessage(Closed, "获取接入网关失败:"+err.Error()))
}
defer g.CloseSshTunnel(s.ID)
exposedIP, exposedPort, err := g.OpenSshTunnel(s.ID, ip, port)
if err != nil {
return WriteMessage(ws, dto.NewMessage(Closed, "创建隧道失败:"+err.Error()))
}
ip = exposedIP
port = exposedPort
}
recording := ""
var isRecording = false
property, err := repository.PropertyRepository.FindByName(ctx, guacamole.EnableRecording)
if err == nil && property.Value == "true" {
isRecording = true
}
if isRecording {
recording = path.Join(config.GlobalCfg.Guacd.Recording, sessionId, "recording.cast")
}
attributes, err := repository.AssetRepository.FindAssetAttrMapByAssetId(ctx, s.AssetId)
if err != nil {
return WriteMessage(ws, dto.NewMessage(Closed, "获取资产属性失败:"+err.Error()))
}
var xterm = "xterm-256color"
var nextTerminal *term.NextTerminal
if "true" == attributes[nt.SocksProxyEnable] {
nextTerminal, err = term.NewNextTerminalUseSocks(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true, attributes[nt.SocksProxyHost], attributes[nt.SocksProxyPort], attributes[nt.SocksProxyUsername], attributes[nt.SocksProxyPassword])
} else {
nextTerminal, err = term.NewNextTerminal(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true)
}
if err != nil {
return WriteMessage(ws, dto.NewMessage(Closed, "创建SSH客户端失败:"+err.Error()))
}
if err := nextTerminal.RequestPty(xterm, rows, cols); err != nil {
return err
}
if err := nextTerminal.Shell(); err != nil {
return err
}
sessionForUpdate := model.Session{
ConnectionId: sessionId,
Width: cols,
Height: rows,
Status: nt.Connected,
Recording: recording,
ConnectedTime: common.NowJsonTime(),
}
if sessionForUpdate.Recording == "" {
// 未录屏时无需审计
sessionForUpdate.Reviewed = true
}
// 更新会话状态
if err := repository.SessionRepository.UpdateById(ctx, &sessionForUpdate, sessionId); err != nil {
return err
}
nextSession := &session.Session{
ID: s.ID,
Protocol: s.Protocol,
Mode: s.Mode,
WebSocket: ws,
GuacdTunnel: nil,
NextTerminal: nextTerminal,
Observer: session.NewObserver(s.ID),
}
session.GlobalSessionManager.Add(nextSession)
termHandler := NewTermHandler(s.Creator, s.AssetId, sessionId, isRecording, ws, nextTerminal)
termHandler.Start()
defer termHandler.Stop()
for {
_, message, err := ws.ReadMessage()
if err != nil {
// web socket会话关闭后主动关闭ssh会话
service.SessionService.CloseSessionById(sessionId, Normal, "用户正常退出")
break
}
msg, err := dto.ParseMessage(string(message))
if err != nil {
continue
}
switch msg.Type {
case Resize:
decodeString, err := base64.StdEncoding.DecodeString(msg.Content)
if err != nil {
continue
}
var winSize dto.WindowSize
err = json.Unmarshal(decodeString, &winSize)
if err != nil {
continue
}
if err := termHandler.WindowChange(winSize.Rows, winSize.Cols); err != nil {
}
_ = repository.SessionRepository.UpdateWindowSizeById(ctx, winSize.Rows, winSize.Cols, sessionId)
case Data:
input := []byte(msg.Content)
err := termHandler.Write(input)
if err != nil {
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
}
case Ping:
err := termHandler.SendRequest()
if err != nil {
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
} else {
_ = termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, ""))
}
}
}
return err
}
func (api WebTerminalApi) SshMonitorEndpoint(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
return err
}
defer func() {
_ = ws.Close()
}()
ctx := context.TODO()
sessionId := c.Param("id")
s, err := repository.SessionRepository.FindById(ctx, sessionId)
if err != nil {
return WriteMessage(ws, dto.NewMessage(Closed, "获取会话失败"))
}
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession == nil {
return WriteMessage(ws, dto.NewMessage(Closed, "会话已离线"))
}
obId := utils.UUID()
obSession := &session.Session{
ID: obId,
Protocol: s.Protocol,
Mode: s.Mode,
WebSocket: ws,
}
nextSession.Observer.Add(obSession)
for {
_, _, err := ws.ReadMessage()
if err != nil {
nextSession.Observer.Del(obId)
break
}
}
return nil
}
func (api WebTerminalApi) AccessTerminalEndpoint(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
return err
}
defer func() {
_ = ws.Close()
}()
sessionId := c.QueryParam("sessionId")
log.Debug("AccessTerminal: WebSocket connected, sessionId="+sessionId+", cols="+c.QueryParam("cols")+", rows="+c.QueryParam("rows"))
if sessionId == "" {
return WriteMessage(ws, dto.NewMessage(Closed, "sessionId is required"))
}
ctx := context.TODO()
s, err := service.SessionService.FindByIdAndDecrypt(ctx, sessionId)
if err != nil {
log.Debug("AccessTerminal: session not found, err=" + err.Error())
return WriteMessage(ws, dto.NewMessage(Closed, "session not found: "+err.Error()))
}
log.Debug("AccessTerminal: session found, protocol=" + s.Protocol + " ip=" + s.IP + ":" + strconv.Itoa(s.Port) + " user=" + s.Username + " pwdLen=" + strconv.Itoa(len(s.Password)) + " keyLen=" + strconv.Itoa(len(s.PrivateKey)))
xterm := "xterm-256color"
cols, _ := strconv.Atoi(c.QueryParam("cols"))
rows, _ := strconv.Atoi(c.QueryParam("rows"))
if cols <= 0 {
cols = 80
}
if rows <= 0 {
rows = 24
}
// 检查是否启用录制
recording := ""
isRecording := false
property, err := repository.PropertyRepository.FindByName(ctx, guacamole.EnableRecording)
if err == nil && property.Value == "true" {
isRecording = true
recording = path.Join(config.GlobalCfg.Guacd.Recording, sessionId, "recording.cast")
}
var nextTerminal *term.NextTerminal
if isRecording {
nextTerminal, err = term.NewNextTerminal(s.IP, s.Port, s.Username, s.Password, s.PrivateKey, s.Passphrase, rows, cols, recording, xterm, true)
} else {
nextTerminal, err = CreateNextTerminalBySession(s)
}
if err != nil {
log.Debug("AccessTerminal: CreateNextTerminalBySession failed, err=" + err.Error())
return WriteMessage(ws, dto.NewMessage(Closed, "创建SSH客户端失败:"+err.Error()))
}
log.Debug("AccessTerminal: SSH client created successfully")
if err := nextTerminal.RequestPty(xterm, rows, cols); err != nil {
log.Debug("AccessTerminal: RequestPty failed, err=" + err.Error())
return WriteMessage(ws, dto.NewMessage(Closed, "请求PTY失败:"+err.Error()))
}
log.Debug("AccessTerminal: RequestPty OK")
if err := nextTerminal.Shell(); err != nil {
log.Debug("AccessTerminal: Shell failed, err=" + err.Error())
return WriteMessage(ws, dto.NewMessage(Closed, "启动Shell失败:"+err.Error()))
}
log.Debug("AccessTerminal: Shell OK, starting TermHandler...")
sessionForUpdate := model.Session{
ConnectionId: sessionId,
Width: cols,
Height: rows,
Status: nt.Connected,
Recording: recording,
ConnectedTime: common.NowJsonTime(),
}
if sessionForUpdate.Recording == "" {
sessionForUpdate.Reviewed = true
}
if err := repository.SessionRepository.UpdateById(ctx, &sessionForUpdate, sessionId); err != nil {
return err
}
nextSession := &session.Session{
ID: s.ID,
Protocol: s.Protocol,
Mode: s.Mode,
WebSocket: ws,
GuacdTunnel: nil,
NextTerminal: nextTerminal,
Observer: session.NewObserver(s.ID),
}
session.GlobalSessionManager.Add(nextSession)
termHandler := NewTermHandler(s.Creator, s.AssetId, sessionId, isRecording, ws, nextTerminal)
termHandler.Start()
defer termHandler.Stop()
log.Debug("AccessTerminal: TermHandler started, entering message loop")
for {
_, message, err := ws.ReadMessage()
if err != nil {
log.Debug("AccessTerminal: WebSocket read error, closing session. err=" + err.Error())
service.SessionService.CloseSessionById(sessionId, Normal, "用户正常退出")
break
}
msg, err := dto.ParseMessage(string(message))
if err != nil {
continue
}
switch msg.Type {
case Resize:
decodeString, _ := base64.StdEncoding.DecodeString(msg.Content)
var winSize dto.WindowSize
json.Unmarshal(decodeString, &winSize)
termHandler.WindowChange(winSize.Rows, winSize.Cols)
repository.SessionRepository.UpdateWindowSizeById(ctx, winSize.Rows, winSize.Cols, sessionId)
case Data:
termHandler.Write([]byte(msg.Content))
case Ping:
termHandler.SendRequest()
termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, ""))
}
}
return nil
}
func (api WebTerminalApi) permissionCheck(c echo.Context, assetId string) error {
user, _ := GetCurrentAccount(c)
if nt.TypeUser == user.Type {
// 检测是否有访问权限 TODO
//assetIds, err := repository.ResourceSharerRepository.FindAssetIdsByUserId(context.TODO(), user.ID)
//if err != nil {
// return err
//}
//
//if !utils.Contains(assetIds, assetId) {
// return errors.New("您没有权限访问此资产")
//}
}
return nil
}
func WriteMessage(ws *websocket.Conn, msg dto.Message) error {
message := []byte(msg.ToString())
return ws.WriteMessage(websocket.TextMessage, message)
}
func CreateNextTerminalBySession(session model.Session) (*term.NextTerminal, error) {
var (
username = session.Username
password = session.Password
privateKey = session.PrivateKey
passphrase = session.Passphrase
ip = session.IP
port = session.Port
)
return term.NewNextTerminal(ip, port, username, password, privateKey, passphrase,
10, 10, "", "", true)
}
+136
View File
@@ -0,0 +1,136 @@
package api
import (
"bytes"
"context"
"sync"
"time"
"unicode/utf8"
"github.com/gorilla/websocket"
"next-terminal/server/common/term"
"next-terminal/server/dto"
"next-terminal/server/global/session"
)
type TermHandler struct {
sessionId string
isRecording bool
webSocket *websocket.Conn
nextTerminal *term.NextTerminal
ctx context.Context
cancel context.CancelFunc
dataChan chan rune
tick *time.Ticker
mutex sync.Mutex
buf bytes.Buffer
}
func NewTermHandler(userId, assetId, sessionId string, isRecording bool, ws *websocket.Conn, nextTerminal *term.NextTerminal) *TermHandler {
ctx, cancel := context.WithCancel(context.Background())
tick := time.NewTicker(time.Millisecond * time.Duration(60))
return &TermHandler{
sessionId: sessionId,
isRecording: isRecording,
webSocket: ws,
nextTerminal: nextTerminal,
ctx: ctx,
cancel: cancel,
dataChan: make(chan rune),
tick: tick,
}
}
func (r *TermHandler) Start() {
go r.readFormTunnel()
go r.writeToWebsocket()
}
func (r *TermHandler) Stop() {
// 会话结束时记录最后一个命令
r.tick.Stop()
r.cancel()
}
func (r *TermHandler) readFormTunnel() {
for {
select {
case <-r.ctx.Done():
return
default:
rn, size, err := r.nextTerminal.StdoutReader.ReadRune()
if err != nil {
return
}
if size > 0 {
r.dataChan <- rn
}
}
}
}
func (r *TermHandler) writeToWebsocket() {
for {
select {
case <-r.ctx.Done():
return
case <-r.tick.C:
s := r.buf.String()
if s == "" {
continue
}
if err := r.SendMessageToWebSocket(dto.NewMessage(Data, s)); err != nil {
return
}
if r.isRecording && r.nextTerminal.Recorder != nil {
_ = r.nextTerminal.Recorder.WriteData(s)
}
// 监控
SendObData(r.sessionId, s)
r.buf.Reset()
case data := <-r.dataChan:
if data != utf8.RuneError {
p := make([]byte, utf8.RuneLen(data))
utf8.EncodeRune(p, data)
r.buf.Write(p)
} else {
r.buf.Write([]byte("@"))
}
}
}
}
func (r *TermHandler) Write(input []byte) error {
// 正常的字符输入
_, err := r.nextTerminal.Write(input)
return err
}
func (r *TermHandler) WindowChange(h int, w int) error {
return r.nextTerminal.WindowChange(h, w)
}
func (r *TermHandler) SendRequest() error {
_, _, err := r.nextTerminal.SshClient.Conn.SendRequest("helloworld1024@foxmail.com", true, nil)
return err
}
func (r *TermHandler) SendMessageToWebSocket(msg dto.Message) error {
if r.webSocket == nil {
return nil
}
defer r.mutex.Unlock()
r.mutex.Lock()
message := []byte(msg.ToString())
return r.webSocket.WriteMessage(websocket.TextMessage, message)
}
func SendObData(sessionId, s string) {
nextSession := session.GlobalSessionManager.GetById(sessionId)
if nextSession != nil && nextSession.Observer != nil {
nextSession.Observer.Range(func(key string, ob *session.Session) {
_ = ob.WriteMessage(dto.NewMessage(Data, s))
})
}
}
+159
View File
@@ -0,0 +1,159 @@
package api
import (
"context"
"next-terminal/server/common/maps"
"strconv"
"strings"
"github.com/labstack/echo/v4"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
)
type UserApi struct{}
func (userApi UserApi) CreateEndpoint(c echo.Context) (err error) {
var item model.User
if err := c.Bind(&item); err != nil {
return err
}
if len(item.Password) > 100 {
return Fail(c, -1, "您输入的密码过长")
}
if err := service.UserService.CreateUser(item); err != nil {
return err
}
return Success(c, item)
}
func (userApi UserApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
username := c.QueryParam("username")
nickname := c.QueryParam("nickname")
mail := c.QueryParam("mail")
order := c.QueryParam("order")
field := c.QueryParam("field")
online := c.QueryParam("online")
items, total, err := repository.UserRepository.Find(context.TODO(), pageIndex, pageSize, username, nickname, mail, online, "", order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (userApi UserApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
//account, _ := GetCurrentAccount(c)
//if account.ID == id {
// return Fail(c, -1, "cannot modify itself")
//}
var item model.User
if err := c.Bind(&item); err != nil {
return err
}
if err := service.UserService.UpdateUser(id, item); err != nil {
return err
}
return Success(c, nil)
}
func (userApi UserApi) UpdateStatusEndpoint(c echo.Context) error {
id := c.Param("id")
status := c.QueryParam("status")
account, _ := GetCurrentAccount(c)
if account.ID == id {
return Fail(c, -1, "不能操作自身账户")
}
if err := service.UserService.UpdateStatusById(id, status); err != nil {
return err
}
return Success(c, nil)
}
func (userApi UserApi) DeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
account, found := GetCurrentAccount(c)
if !found {
return Fail(c, -1, "获取当前登录账户失败")
}
split := strings.Split(ids, ",")
for i := range split {
userId := split[i]
if account.ID == userId {
return Fail(c, -1, "不允许删除自身账户")
}
if err := service.UserService.DeleteUserById(userId); err != nil {
return err
}
}
return Success(c, nil)
}
func (userApi UserApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := service.UserService.FindById(id)
if err != nil {
return err
}
return Success(c, item)
}
func (userApi UserApi) ChangePasswordEndpoint(c echo.Context) error {
id := c.Param("id")
password := c.FormValue("password")
if password == "" {
return Fail(c, -1, "请输入密码")
}
ids := strings.Split(id, ",")
if err := service.UserService.ChangePassword(ids, password); err != nil {
return err
}
return Success(c, "")
}
func (userApi UserApi) ResetTotpEndpoint(c echo.Context) error {
id := c.Param("id")
ids := strings.Split(id, ",")
if err := service.UserService.ResetTotp(ids); err != nil {
return err
}
return Success(c, "")
}
func (userApi UserApi) AllEndpoint(c echo.Context) error {
users, err := repository.UserRepository.FindAll(context.Background())
if err != nil {
return err
}
items := make([]maps.Map, len(users))
for i, user := range users {
items[i] = maps.Map{
"id": user.ID,
"nickname": user.Nickname,
}
}
return Success(c, items)
}
+108
View File
@@ -0,0 +1,108 @@
package api
import (
"context"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"strconv"
"strings"
"next-terminal/server/dto"
"next-terminal/server/repository"
"next-terminal/server/service"
"github.com/labstack/echo/v4"
)
type UserGroupApi struct{}
func (userGroupApi UserGroupApi) UserGroupCreateEndpoint(c echo.Context) error {
var item model.UserGroup
if err := c.Bind(&item); err != nil {
return err
}
if _, err := service.UserGroupService.Create(context.TODO(), item.Name, item.Members); err != nil {
return err
}
return Success(c, item)
}
func (userGroupApi UserGroupApi) UserGroupPagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.UserGroupRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (userGroupApi UserGroupApi) UserGroupUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item model.UserGroup
if err := c.Bind(&item); err != nil {
return err
}
if err := service.UserGroupService.Update(id, item.Name, item.Members); err != nil {
return err
}
return Success(c, nil)
}
func (userGroupApi UserGroupApi) UserGroupDeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
split := strings.Split(ids, ",")
for i := range split {
userId := split[i]
if err := service.UserGroupService.DeleteById(userId); err != nil {
return err
}
}
return Success(c, nil)
}
func (userGroupApi UserGroupApi) UserGroupGetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.UserGroupRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
members, err := repository.UserGroupMemberRepository.FindByUserGroupId(context.TODO(), id)
if err != nil {
return err
}
userGroup := dto.UserGroup{
Id: item.ID,
Name: item.Name,
Created: item.Created,
Members: members,
}
return Success(c, userGroup)
}
func (userGroupApi UserGroupApi) UserGroupAllEndpoint(c echo.Context) error {
userGroups, err := repository.UserGroupRepository.FindAll(context.Background())
if err != nil {
return err
}
return Success(c, userGroups)
}
+180
View File
@@ -0,0 +1,180 @@
package api
import (
"context"
"encoding/json"
"strconv"
"next-terminal/server/common/maps"
"next-terminal/server/dto"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type WebsiteApi struct{}
func (api WebsiteApi) AllEndpoint(c echo.Context) error {
items, err := repository.WebsiteRepository.FindAll(context.TODO())
if err != nil {
return err
}
return Success(c, items)
}
func (api WebsiteApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
keyword := c.QueryParam("keyword")
items, total, err := repository.WebsiteRepository.Find(context.TODO(), pageIndex, pageSize, keyword)
if err != nil {
return err
}
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func serializeJSON(v interface{}) string {
if v == nil {
return ""
}
switch val := v.(type) {
case string:
return val
case []interface{}, map[string]interface{}:
bytes, err := json.Marshal(val)
if err != nil {
return ""
}
return string(bytes)
default:
bytes, err := json.Marshal(val)
if err != nil {
return ""
}
return string(bytes)
}
}
func (api WebsiteApi) CreateEndpoint(c echo.Context) error {
var req dto.WebsiteDTO
if err := c.Bind(&req); err != nil {
return err
}
item := &model.Website{
ID: utils.UUID(),
Name: req.Name,
Enabled: req.Enabled,
TargetUrl: req.TargetUrl,
TargetHost: req.TargetHost,
TargetPort: req.TargetPort,
Domain: req.Domain,
AsciiDomain: req.AsciiDomain,
Entrance: req.Entrance,
Description: req.Description,
Status: req.Status,
StatusText: req.StatusText,
GatewayType: req.GatewayType,
GatewayId: req.GatewayId,
BasicAuth: serializeJSON(req.BasicAuth),
Headers: serializeJSON(req.Headers),
Cert: serializeJSON(req.Cert),
Public: serializeJSON(req.Public),
TempAllow: serializeJSON(req.TempAllow),
GroupId: req.GroupId,
Sort: req.Sort,
}
if err := repository.WebsiteRepository.Create(context.TODO(), item); err != nil {
return err
}
return Success(c, "")
}
func (api WebsiteApi) UpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var req dto.WebsiteDTO
if err := c.Bind(&req); err != nil {
return err
}
item := &model.Website{
ID: id,
Name: req.Name,
Enabled: req.Enabled,
TargetUrl: req.TargetUrl,
TargetHost: req.TargetHost,
TargetPort: req.TargetPort,
Domain: req.Domain,
AsciiDomain: req.AsciiDomain,
Entrance: req.Entrance,
Description: req.Description,
Status: req.Status,
StatusText: req.StatusText,
GatewayType: req.GatewayType,
GatewayId: req.GatewayId,
BasicAuth: serializeJSON(req.BasicAuth),
Headers: serializeJSON(req.Headers),
Cert: serializeJSON(req.Cert),
Public: serializeJSON(req.Public),
TempAllow: serializeJSON(req.TempAllow),
GroupId: req.GroupId,
Sort: req.Sort,
}
if err := repository.WebsiteRepository.UpdateById(context.TODO(), item, id); err != nil {
return err
}
return Success(c, nil)
}
func (api WebsiteApi) DeleteEndpoint(c echo.Context) error {
id := c.Param("id")
if err := repository.WebsiteRepository.DeleteById(context.TODO(), id); err != nil {
return err
}
return Success(c, nil)
}
func (api WebsiteApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.WebsiteRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
return Success(c, item)
}
func (api WebsiteApi) GroupsGetEndpoint(c echo.Context) error {
return Success(c, []interface{}{})
}
func (api WebsiteApi) GroupsSetEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api WebsiteApi) GroupsDeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api WebsiteApi) ChangeGroupEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api WebsiteApi) ChangeGatewayEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api WebsiteApi) SortEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (api WebsiteApi) FaviconEndpoint(c echo.Context) error {
return Success(c, "")
}
+49
View File
@@ -0,0 +1,49 @@
package worker
import (
"context"
"github.com/labstack/echo/v4"
"next-terminal/server/api/abi"
"next-terminal/server/common/maps"
"next-terminal/server/service"
"strconv"
)
type WorkAssetApi struct {
abi.Abi
}
func (api WorkAssetApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
protocol := c.QueryParam("protocol")
tags := c.QueryParam("tags")
order := c.QueryParam("order")
field := c.QueryParam("field")
account, _ := api.GetCurrentAccount(c)
items, total, err := service.WorkerService.FindMyAssetPaging(pageIndex, pageSize, name, protocol, tags, account.ID, order, field)
if err != nil {
return err
}
for i := range items {
items[i].IP = ""
items[i].Port = 0
}
return api.Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api WorkAssetApi) TagsEndpoint(c echo.Context) (err error) {
account, _ := api.GetCurrentAccount(c)
var items []string
if items, err = service.WorkerService.FindMyAssetTags(context.TODO(), account.ID); err != nil {
return err
}
return api.Success(c, items)
}
+132
View File
@@ -0,0 +1,132 @@
package worker
import (
"context"
"errors"
"gorm.io/gorm"
"next-terminal/server/common/nt"
"strconv"
"strings"
"next-terminal/server/api/abi"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type WorkCommandApi struct {
abi.Abi
}
func (api WorkCommandApi) CommandCreateEndpoint(c echo.Context) error {
var item model.Command
if err := c.Bind(&item); err != nil {
return err
}
account, _ := api.GetCurrentAccount(c)
item.Owner = account.ID
item.ID = utils.UUID()
item.Created = common.NowJsonTime()
if err := repository.CommandRepository.Create(context.TODO(), &item); err != nil {
return err
}
return api.Success(c, item)
}
func (api WorkCommandApi) CommandAllEndpoint(c echo.Context) error {
account, _ := api.GetCurrentAccount(c)
userId := account.ID
items, err := repository.CommandRepository.FindByUserId(context.Background(), userId)
if err != nil {
return err
}
return api.Success(c, items)
}
func (api WorkCommandApi) 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")
order := c.QueryParam("order")
field := c.QueryParam("field")
account, _ := api.GetCurrentAccount(c)
userId := account.ID
items, total, err := repository.CommandRepository.WorkerFind(context.TODO(), pageIndex, pageSize, name, content, order, field, userId)
if err != nil {
return err
}
return api.Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api WorkCommandApi) CommandUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
if !api.checkPermission(c, id) {
return nt.ErrPermissionDenied
}
var item model.Command
if err := c.Bind(&item); err != nil {
return err
}
if err := repository.CommandRepository.UpdateById(context.TODO(), &item, id); err != nil {
return err
}
return api.Success(c, nil)
}
func (api WorkCommandApi) CommandDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
split := strings.Split(id, ",")
for i := range split {
if !api.checkPermission(c, id) {
return nt.ErrPermissionDenied
}
if err := repository.CommandRepository.DeleteById(context.TODO(), split[i]); err != nil {
return err
}
}
return api.Success(c, nil)
}
func (api WorkCommandApi) CommandGetEndpoint(c echo.Context) (err error) {
id := c.Param("id")
if !api.checkPermission(c, id) {
return nt.ErrPermissionDenied
}
var item model.Command
if item, err = repository.CommandRepository.FindById(context.TODO(), id); err != nil {
return err
}
return api.Success(c, item)
}
func (api WorkCommandApi) checkPermission(c echo.Context, commandId string) bool {
command, err := repository.CommandRepository.FindById(context.Background(), commandId)
if err != nil {
if errors.Is(gorm.ErrRecordNotFound, err) {
return true
}
return false
}
account, _ := api.GetCurrentAccount(c)
userId := account.ID
return command.Owner == userId
}