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
}
+144
View File
@@ -0,0 +1,144 @@
package app
import (
"encoding/json"
"fmt"
"next-terminal/server/branding"
"next-terminal/server/config"
"next-terminal/server/service"
"next-terminal/server/sshd"
"next-terminal/server/task"
"github.com/labstack/echo/v4"
)
var app *App
type App struct {
Server *echo.Echo
}
func newApp() *App {
return &App{}
}
func init() {
setupCache()
app = newApp()
}
func (app App) InitDBData() (err error) {
if err := service.PropertyService.DeleteDeprecatedProperty(); err != nil {
return err
}
if err := service.GatewayService.LoadAll(); err != nil {
return err
}
if err := service.PropertyService.InitProperties(); err != nil {
return err
}
if err := service.UserService.InitUser(); err != nil {
return err
}
if err := service.JobService.InitJob(); err != nil {
return err
}
if err := service.UserService.FixUserOnlineState(); err != nil {
return err
}
if err := service.SessionService.FixSessionState(); err != nil {
return err
}
if err := service.SessionService.EmptyPassword(); err != nil {
return err
}
if err := service.CredentialService.EncryptAll(); err != nil {
return err
}
if err := service.AssetService.EncryptAll(); err != nil {
return err
}
if err := service.StorageService.InitStorages(); err != nil {
return err
}
if err := service.MenuService.Init(); err != nil {
return err
}
if err := service.RoleService.Init(); err != nil {
return err
}
// 修复数据
if err := service.AssetService.FixSshMode(); err != nil {
return err
}
if err := service.SessionService.FixSshMode(); err != nil {
return err
}
if err := service.MigrateService.Migrate(); err != nil {
return err
}
return nil
}
func (app App) ReloadData() error {
if err := service.SecurityService.ReloadAccessSecurity(); err != nil {
return err
}
if err := service.UserService.ReloadToken(); err != nil {
return err
}
if err := service.AccessTokenService.Reload(); err != nil {
return err
}
return nil
}
func Run() error {
fmt.Printf(branding.Hi)
if err := app.InitDBData(); err != nil {
panic(err)
}
if err := app.ReloadData(); err != nil {
panic(err)
}
app.Server = setupRoutes()
if config.GlobalCfg.Debug {
jsonBytes, err := json.MarshalIndent(config.GlobalCfg, "", " ")
if err != nil {
return err
}
fmt.Printf("当前配置为: %v\n", string(jsonBytes))
}
_cli := service.NewCli()
if config.GlobalCfg.ResetPassword != "" {
return _cli.ResetPassword(config.GlobalCfg.ResetPassword)
}
if config.GlobalCfg.ResetTotp != "" {
return _cli.ResetTotp(config.GlobalCfg.ResetTotp)
}
if config.GlobalCfg.NewEncryptionKey != "" {
return _cli.ChangeEncryptionKey(config.GlobalCfg.EncryptionKey, config.GlobalCfg.NewEncryptionKey)
}
ticker := task.NewTicker()
ticker.SetupTicker()
if config.GlobalCfg.Sshd.Enable {
go sshd.Sshd.Serve()
}
if config.GlobalCfg.Server.Cert != "" && config.GlobalCfg.Server.Key != "" {
return app.Server.StartTLS(config.GlobalCfg.Server.Addr, config.GlobalCfg.Server.Cert, config.GlobalCfg.Server.Key)
} else {
return app.Server.Start(config.GlobalCfg.Server.Addr)
}
}
+10
View File
@@ -0,0 +1,10 @@
package app
import (
"next-terminal/server/global/cache"
"next-terminal/server/service"
)
func setupCache() {
cache.TokenManager.OnEvicted(service.UserService.OnEvicted)
}
+157
View File
@@ -0,0 +1,157 @@
package middleware
import (
"next-terminal/server/common/nt"
"strings"
"next-terminal/server/api"
"next-terminal/server/dto"
"next-terminal/server/global/cache"
"next-terminal/server/service"
"github.com/labstack/echo/v4"
"github.com/ucarion/urlpath"
)
var anonymousUrls = []string{"/login", "/static", "/favicon.ico", "/logo.svg", "/logo", "/branding", "/api/branding", "/api/logo", "/setup-status", "/setup-user", "/api/admin/setup-status", "/api/admin/login", "/api/admin/setup-user", "/api/admin/login-status", "/api/login-status", "/api/admin/captcha", "/api/captcha", "/captcha"}
var allowUrls = []urlpath.Path{
urlpath.New("/api/admin/account/info"),
urlpath.New("/api/admin/account/storage"),
urlpath.New("/api/admin/account/logout"),
urlpath.New("/api/admin/account/change-password"),
urlpath.New("/api/admin/account/reload-totp"),
urlpath.New("/api/admin/account/reset-totp"),
urlpath.New("/api/admin/account/confirm-totp"),
urlpath.New("/api/admin/account/access-token"),
urlpath.New("/api/admin/account/security-token/support-types"),
urlpath.New("/api/admin/account/security-token/mfa"),
urlpath.New("/api/admin/account/security-token/validate"),
urlpath.New("/api/admin/account/client-cert"),
urlpath.New("/api/admin/account/client-cert/download"),
urlpath.New("/api/admin/account/webauthn/credentials"),
urlpath.New("/api/admin/account/webauthn/credentials/:id"),
urlpath.New("/api/admin/account/webauthn/credentials/start"),
urlpath.New("/api/admin/account/webauthn/credentials/finish"),
urlpath.New("/api/admin/account/oidc-server-consents"),
urlpath.New("/api/admin/account/oidc-server-consents/:id"),
urlpath.New("/share-sessions/:id"),
urlpath.New("/api/admin/sessions"),
urlpath.New("/api/admin/sessions/:id/tunnel"),
urlpath.New("/api/admin/sessions/:id/connect"),
urlpath.New("/api/admin/sessions/:id/resize"),
urlpath.New("/api/admin/sessions/:id/stats"),
urlpath.New("/api/admin/sessions/:id/ls"),
urlpath.New("/api/admin/sessions/:id/download"),
urlpath.New("/api/admin/sessions/:id/upload"),
urlpath.New("/api/admin/sessions/:id/edit"),
urlpath.New("/api/admin/sessions/:id/mkdir"),
urlpath.New("/api/admin/sessions/:id/rm"),
urlpath.New("/api/admin/sessions/:id/rename"),
urlpath.New("/api/admin/sessions/:id/ssh"),
urlpath.New("/api/portal/assets"),
urlpath.New("/api/portal/database-assets"),
urlpath.New("/api/portal/assets/tree"),
urlpath.New("/api/portal/websites/tree"),
urlpath.New("/api/portal/assets/group-tree"),
urlpath.New("/api/portal/websites"),
urlpath.New("/api/portal/info"),
urlpath.New("/api/license"),
}
func Auth(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
uri := c.Request().RequestURI
if uri == "/" || strings.HasPrefix(uri, "/#") {
return next(c)
}
// 路由拦截 - 登录身份、资源权限判断等
for i := range anonymousUrls {
if strings.HasPrefix(uri, anonymousUrls[i]) {
return next(c)
}
}
token := api.GetToken(c)
if token == "" {
return api.Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
}
v, found := cache.TokenManager.Get(token)
if !found {
return api.Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
}
authorization := v.(dto.Authorization)
if strings.EqualFold(nt.LoginToken, authorization.Type) {
if authorization.Remember {
// 记住登录有效期两周
cache.TokenManager.Set(token, authorization, cache.RememberMeExpiration)
} else {
cache.TokenManager.Set(token, authorization, cache.NotRememberExpiration)
}
}
// 放行接入相关接口
uriPath := strings.Split(uri, "?")[0]
for _, url := range allowUrls {
_, ok := url.Match(uriPath)
if ok {
return next(c)
}
}
account, _ := api.GetCurrentAccount(c)
if service.UserService.IsSuperAdmin(account.ID) {
return next(c)
}
var roles []string
v, ok := cache.UserRolesManager.Get(account.ID)
if ok {
roles = v.([]string)
if len(roles) == 0 {
roles, _ = service.RoleService.GetRolesByUserId(account.ID)
cache.UserRolesManager.SetDefault(account.ID, roles)
}
} else {
roles, _ = service.RoleService.GetRolesByUserId(account.ID)
cache.UserRolesManager.SetDefault(account.ID, roles)
}
urlPath := c.Request().URL.Path
for _, role := range roles {
menus := service.RoleService.GetMenuListByRole(role)
for _, menu := range menus {
permissions := service.MenuService.GetPermissionByMenu(menu)
for _, perm := range permissions {
_, ok := perm.Match(urlPath)
if ok {
return next(c)
}
}
}
}
return api.Fail(c, 403, "permission denied")
}
}
func Admin(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
account, found := api.GetCurrentAccount(c)
if !found {
return api.Fail(c, 401, "您的登录信息已失效,请重新登录后再试。")
}
if account.Type != nt.TypeAdmin {
return api.Fail(c, 403, "permission denied.")
}
return next(c)
}
}
+28
View File
@@ -0,0 +1,28 @@
package middleware
import (
"fmt"
"next-terminal/server/log"
"next-terminal/server/api"
"github.com/labstack/echo/v4"
)
func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if err := next(c); err != nil {
fmt.Printf("%+v\n", err)
log.Error("api error", log.NamedError("err", err))
if he, ok := err.(*echo.HTTPError); ok {
message := fmt.Sprintf("%v", he.Message)
return api.Fail(c, he.Code, message)
}
return api.Fail(c, -1, err.Error())
}
return nil
}
}
+72
View File
@@ -0,0 +1,72 @@
package middleware
import (
"net"
"next-terminal/server/common/nt"
"strings"
"next-terminal/server/api"
"next-terminal/server/global/security"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
func TcpWall(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
securities := security.GlobalSecurityManager.Values()
if len(securities) == 0 {
return next(c)
}
ip := c.RealIP()
var pass = true
for _, s := range securities {
ipGroups := strings.Split(s.IP, ",")
for _, ipGroup := range ipGroups {
if strings.Contains(ipGroup, "/") {
// CIDR
_, ipNet, err := net.ParseCIDR(ipGroup)
if err != nil {
continue
}
if !ipNet.Contains(net.ParseIP(ip)) {
continue
}
} else if strings.Contains(ipGroup, "-") {
// 范围段
split := strings.Split(ipGroup, "-")
if len(split) < 2 {
continue
}
start := split[0]
end := split[1]
intReqIP := utils.IpToInt(ip)
if intReqIP < utils.IpToInt(start) || intReqIP > utils.IpToInt(end) {
continue
}
} else {
// IP
if ipGroup != ip {
continue
}
}
pass = s.Rule == nt.AccessRuleAllow
}
}
if !pass {
if c.Request().Header.Get("X-Requested-With") != "" || c.Request().Header.Get(nt.Token) != "" {
return api.Fail(c, -1, "您的访问请求被拒绝 :(")
} else {
return c.HTML(666, "您的访问请求被拒绝 :(")
}
}
return next(c)
}
}
+791
View File
@@ -0,0 +1,791 @@
package app
import (
"io/fs"
"net/http"
"os"
"next-terminal/server/api"
"next-terminal/server/api/worker"
mw "next-terminal/server/app/middleware"
"next-terminal/server/config"
"next-terminal/server/log"
"next-terminal/server/resource"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func getFS(useOS bool) fs.FS {
if useOS {
log.Debug("using live mode")
return os.DirFS("web/build")
}
log.Debug("using embed mode")
fsys, err := fs.Sub(resource.Resource, "build")
if err != nil {
panic(err)
}
return fsys
}
func WrapHandler(h http.Handler) echo.HandlerFunc {
return func(c echo.Context) error {
c.Response().Header().Set("Cache-Control", `public, max-age=31536000`)
h.ServeHTTP(c.Response(), c.Request())
return nil
}
}
func setupRoutes() *echo.Echo {
e := echo.New()
e.HideBanner = true
//e.Logger = log.GetEchoLogger()
//e.Use(log.Hook())
fsys := getFS(config.GlobalCfg.Debug)
fileServer := http.FileServer(http.FS(fsys))
handler := WrapHandler(fileServer)
e.GET("/", handler)
e.GET("/branding", api.Branding)
e.GET("/logo", api.Logo)
e.GET("/favicon.ico", handler)
e.GET("/static/*", handler)
e.Use(middleware.Recover())
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
Skipper: middleware.DefaultSkipper,
AllowOrigins: []string{"*"},
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
}))
e.Use(mw.ErrorHandler)
e.Use(mw.TcpWall)
setupApi := new(api.SetupApi)
accountApi := new(api.AccountApi)
apiGroup := e.Group("/api")
{
apiGroup.GET("/branding", api.Branding)
apiGroup.GET("/logo", api.Logo)
adminGroup := apiGroup.Group("/admin")
{
adminGroup.POST("/login", accountApi.LoginEndpoint)
adminGroup.GET("/setup-status", setupApi.GetSetupStatusEndpoint)
adminGroup.POST("/setup-user", setupApi.SetupUserEndpoint)
}
}
e.Use(mw.Auth)
//e.Use(RBAC)
e.Use(middleware.Gzip())
apiGroup = e.Group("/api")
{
AgentGatewayApi := new(api.AgentGatewayApi)
apiGroup.GET("/branding", api.Branding)
apiGroup.GET("/logo", api.Logo)
apiGroup.GET("/agent/version", AgentGatewayApi.VersionEndpoint)
adminGroup := apiGroup.Group("/admin")
PortalApi := new(api.PortalApi)
AccessSettingApi := new(api.AccessSettingApi)
webTerminalApi := new(api.WebTerminalApi)
FileSystemApi := new(api.FileSystemApi)
{
guacamoleApi := new(api.GuacamoleApi)
UserApi := new(api.UserApi)
UserGroupApi := new(api.UserGroupApi)
AssetApi := new(api.AssetApi)
CommandApi := new(api.CommandApi)
CredentialApi := new(api.CredentialApi)
SessionApi := new(api.SessionApi)
LoginLogApi := new(api.LoginLogApi)
PropertyApi := new(api.PropertyApi)
LogoApi := new(api.LogoApi)
OverviewApi := new(api.OverviewApi)
JobApi := new(api.JobApi)
SecurityApi := new(api.SecurityApi)
StorageApi := new(api.StorageApi)
StrategyApi := new(api.StrategyApi)
AccessGatewayApi := new(api.AccessGatewayApi)
BackupApi := new(api.BackupApi)
TenantApi := new(api.TenantApi)
RoleApi := new(api.RoleApi)
LoginPolicyApi := new(api.LoginPolicyApi)
StorageLogApi := new(api.StorageLogApi)
AuthorisedApi := new(api.AuthorisedApi)
DashboardApi := new(api.DashboardApi)
DepartmentApi := new(api.DepartmentApi)
SnippetApi := new(api.SnippetApi)
CommandFilterApi := new(api.CommandFilterApi)
DatabaseAssetApi := new(api.DatabaseAssetApi)
AssetGroupApi := new(api.AssetGroupApi)
SshGatewayApi := new(api.SshGatewayApi)
GatewayGroupApi := new(api.GatewayGroupApi)
WebsiteApi := new(api.WebsiteApi)
CertificateApi := new(api.CertificateApi)
adminGroup.GET("/login-status", setupApi.LoginStatusEndpoint)
adminGroup.POST("/validate-totp", setupApi.ValidateTOTPEndpoint)
adminGroup.GET("/account/password-policy", setupApi.PasswordPolicyEndpoint)
adminGroup.GET("/captcha", setupApi.GetCaptchaEndpoint)
dashboard := adminGroup.Group("/dashboard")
{
dashboard.GET("/time-counter", DashboardApi.GetTimeCounterEndpoint)
dashboard.GET("/v2/date-counter", DashboardApi.GetDateCounterV2Endpoint)
dashboard.GET("/asset-types", DashboardApi.GetAssetTypesEndpoint)
}
departments := adminGroup.Group("/departments")
{
departments.GET("", DepartmentApi.AllEndpoint)
departments.GET("/paging", DepartmentApi.PagingEndpoint)
departments.POST("", DepartmentApi.CreateEndpoint)
departments.PUT("/:id", DepartmentApi.UpdateEndpoint)
departments.DELETE("/:id", DepartmentApi.DeleteEndpoint)
departments.GET("/:id", DepartmentApi.GetEndpoint)
departments.GET("/tree", DepartmentApi.GetTreeEndpoint)
departments.GET("/:id/users", DepartmentApi.GetDepartmentUsersEndpoint)
departments.POST("/:id/users", DepartmentApi.SetDepartmentUsersEndpoint)
departments.POST("/:id/remove-users", DepartmentApi.RemoveUsersFromDepartmentEndpoint)
}
snippets := adminGroup.Group("/snippets")
{
snippets.GET("", SnippetApi.AllEndpoint)
snippets.GET("/paging", SnippetApi.PagingEndpoint)
snippets.POST("", SnippetApi.CreateEndpoint)
snippets.PUT("/:id", SnippetApi.UpdateEndpoint)
snippets.DELETE("/:id", SnippetApi.DeleteEndpoint)
snippets.GET("/:id", SnippetApi.GetEndpoint)
}
commandFilters := adminGroup.Group("/command-filters")
{
commandFilters.GET("", CommandFilterApi.AllEndpoint)
commandFilters.GET("/paging", CommandFilterApi.PagingEndpoint)
commandFilters.POST("", CommandFilterApi.CreateEndpoint)
commandFilters.PUT("/:id", CommandFilterApi.UpdateEndpoint)
commandFilters.DELETE("/:id", CommandFilterApi.DeleteEndpoint)
commandFilters.GET("/:id", CommandFilterApi.GetEndpoint)
commandFilters.POST("/:id/bind", CommandFilterApi.BindEndpoint)
commandFilters.POST("/:id/unbind", CommandFilterApi.UnbindEndpoint)
}
databaseAssets := adminGroup.Group("/database-assets")
{
databaseAssets.GET("", DatabaseAssetApi.AllEndpoint)
databaseAssets.GET("/paging", DatabaseAssetApi.PagingEndpoint)
databaseAssets.POST("", DatabaseAssetApi.CreateEndpoint)
databaseAssets.PUT("/:id", DatabaseAssetApi.UpdateEndpoint)
databaseAssets.DELETE("/:id", DatabaseAssetApi.DeleteEndpoint)
databaseAssets.GET("/:id", DatabaseAssetApi.GetEndpoint)
databaseAssets.GET("/:id/decrypted", DatabaseAssetApi.DecryptEndpoint)
}
account := adminGroup.Group("/account")
{
account.GET("/info", accountApi.InfoEndpoint)
account.GET("/storage", accountApi.AccountStorageEndpoint)
account.POST("/logout", accountApi.LogoutEndpoint)
account.POST("/change-password", accountApi.ChangePasswordEndpoint)
account.GET("/reload-totp", accountApi.ReloadTOTPEndpoint)
account.POST("/reset-totp", accountApi.ResetTOTPEndpoint)
account.POST("/confirm-totp", accountApi.ConfirmTOTPEndpoint)
account.GET("/access-token", accountApi.AccessTokenGetEndpoint)
account.POST("/access-token", accountApi.AccessTokenGenEndpoint)
account.DELETE("/access-token/:id", accountApi.AccessTokenDelEndpoint)
account.GET("/security-token/support-types", accountApi.SecurityTokenSupportTypesEndpoint)
account.POST("/security-token/mfa", accountApi.SecurityTokenMfaEndpoint)
account.POST("/security-token/validate", accountApi.SecurityTokenValidateEndpoint)
}
_worker := adminGroup.Group("/worker")
{
commands := _worker.Group("/commands")
{
workerCommandApi := new(worker.WorkCommandApi)
commands.GET("", workerCommandApi.CommandAllEndpoint)
commands.GET("/paging", workerCommandApi.CommandPagingEndpoint)
commands.POST("", workerCommandApi.CommandCreateEndpoint)
commands.PUT("/:id", workerCommandApi.CommandUpdateEndpoint)
commands.DELETE("/:id", workerCommandApi.CommandDeleteEndpoint)
commands.GET("/:id", workerCommandApi.CommandGetEndpoint)
}
assets := _worker.Group("/assets")
{
workAssetApi := new(worker.WorkAssetApi)
assets.GET("/paging", workAssetApi.PagingEndpoint)
assets.GET("/tags", workAssetApi.TagsEndpoint)
}
}
users := adminGroup.Group("/users", mw.Admin)
{
users.GET("", UserApi.AllEndpoint)
users.GET("/paging", UserApi.PagingEndpoint)
users.POST("", UserApi.CreateEndpoint)
users.PUT("/:id", UserApi.UpdateEndpoint)
users.PATCH("/:id/status", UserApi.UpdateStatusEndpoint)
users.DELETE("/:id", UserApi.DeleteEndpoint)
users.GET("/:id", UserApi.GetEndpoint)
users.POST("/:id/change-password", UserApi.ChangePasswordEndpoint)
users.POST("/:id/reset-totp", UserApi.ResetTotpEndpoint)
}
userGroups := adminGroup.Group("/user-groups", mw.Admin)
{
userGroups.POST("", UserGroupApi.UserGroupCreateEndpoint)
userGroups.GET("", UserGroupApi.UserGroupAllEndpoint)
userGroups.GET("/paging", UserGroupApi.UserGroupPagingEndpoint)
userGroups.PUT("/:id", UserGroupApi.UserGroupUpdateEndpoint)
userGroups.DELETE("/:id", UserGroupApi.UserGroupDeleteEndpoint)
userGroups.GET("/:id", UserGroupApi.UserGroupGetEndpoint)
}
assets := adminGroup.Group("/assets")
{
assets.GET("", AssetApi.AssetAllEndpoint)
assets.POST("", AssetApi.AssetCreateEndpoint)
assets.POST("/import", AssetApi.AssetImportEndpoint)
assets.GET("/paging", AssetApi.AssetPagingEndpoint)
assets.GET("/tags", AssetApi.AssetTagsEndpoint)
assets.GET("/groups", AssetGroupApi.GroupsGetEndpoint)
assets.PUT("/groups", AssetGroupApi.GroupsSetEndpoint)
assets.DELETE("/groups/:id", AssetGroupApi.GroupsDeleteEndpoint)
assets.GET("/tree", AssetGroupApi.TreeEndpoint)
assets.POST("/change-group", AssetGroupApi.ChangeGroupEndpoint)
assets.POST("/change-gateway", AssetGroupApi.ChangeGatewayEndpoint)
assets.POST("/sort", AssetGroupApi.SortEndpoint)
assets.GET("/logos", LogoApi.LogosEndpoint)
assets.POST("/:id/tcping", AssetApi.AssetTcpingEndpoint)
assets.PUT("/:id", AssetApi.AssetUpdateEndpoint)
assets.GET("/:id/decrypted", AssetApi.AssetDecryptedEndpoint)
assets.GET("/:id", AssetApi.AssetGetEndpoint)
assets.DELETE("/:id", AssetApi.AssetDeleteEndpoint)
assets.POST("/:id/change-owner", AssetApi.AssetChangeOwnerEndpoint)
}
adminGroup.GET("/tags", AssetApi.AssetTagsEndpoint)
commands := adminGroup.Group("/commands")
{
commands.GET("", CommandApi.CommandAllEndpoint)
commands.GET("/paging", CommandApi.CommandPagingEndpoint)
commands.POST("", CommandApi.CommandCreateEndpoint)
commands.PUT("/:id", CommandApi.CommandUpdateEndpoint)
commands.DELETE("/:id", CommandApi.CommandDeleteEndpoint)
commands.GET("/:id", CommandApi.CommandGetEndpoint)
commands.POST("/:id/change-owner", CommandApi.CommandChangeOwnerEndpoint)
}
credentials := adminGroup.Group("/credentials")
{
credentials.GET("", CredentialApi.CredentialAllEndpoint)
credentials.GET("/paging", CredentialApi.CredentialPagingEndpoint)
credentials.POST("", CredentialApi.CredentialCreateEndpoint)
credentials.POST("/gen-private-key", CredentialApi.GenPrivateKeyEndpoint)
credentials.PUT("/:id", CredentialApi.CredentialUpdateEndpoint)
credentials.DELETE("/:id", CredentialApi.CredentialDeleteEndpoint)
credentials.GET("/:id", CredentialApi.CredentialGetEndpoint)
credentials.GET("/:id/public-key", CredentialApi.GetPublicKeyEndpoint)
credentials.GET("/:id/decrypted", CredentialApi.DecryptedEndpoint)
credentials.POST("/:id/change-owner", CredentialApi.CredentialChangeOwnerEndpoint)
}
sessions := adminGroup.Group("/sessions")
{
sessions.GET("/paging", SessionApi.SessionPagingEndpoint)
sessions.POST("/:id/disconnect", SessionApi.SessionDisconnectEndpoint)
sessions.DELETE("/:id", SessionApi.SessionDeleteEndpoint)
sessions.GET("/:id/recording", SessionApi.SessionRecordingEndpoint)
sessions.GET("/:id", SessionApi.SessionGetEndpoint)
sessions.POST("/:id/reviewed", SessionApi.SessionReviewedEndpoint)
sessions.POST("/:id/unreviewed", SessionApi.SessionUnViewedEndpoint)
sessions.POST("/clear", SessionApi.SessionClearEndpoint)
sessions.POST("/reviewed", SessionApi.SessionReviewedAllEndpoint)
sessions.POST("", SessionApi.SessionCreateEndpoint)
sessions.POST("/:id/connect", SessionApi.SessionConnectEndpoint)
sessions.GET("/:id/tunnel", guacamoleApi.Guacamole)
sessions.GET("/:id/tunnel-monitor", guacamoleApi.GuacamoleMonitor)
sessions.GET("/:id/ssh", webTerminalApi.SshEndpoint)
sessions.GET("/:id/ssh-monitor", webTerminalApi.SshMonitorEndpoint)
sessions.POST("/:id/resize", SessionApi.SessionResizeEndpoint)
sessions.GET("/:id/stats", SessionApi.SessionStatsEndpoint)
sessions.POST("/:id/ls", SessionApi.SessionLsEndpoint)
sessions.GET("/:id/download", SessionApi.SessionDownloadEndpoint)
sessions.POST("/:id/upload", SessionApi.SessionUploadEndpoint)
sessions.POST("/:id/edit", SessionApi.SessionEditEndpoint)
sessions.POST("/:id/mkdir", SessionApi.SessionMkDirEndpoint)
sessions.POST("/:id/rm", SessionApi.SessionRmEndpoint)
sessions.POST("/:id/rename", SessionApi.SessionRenameEndpoint)
sessions.GET("/audit-enabled", SessionApi.SessionAuditEnabledEndpoint)
sessions.POST("/:id/audit", SessionApi.SessionTriggerAuditEndpoint)
sessions.GET("/:id/audit", SessionApi.SessionGetAuditEndpoint)
}
loginLogs := adminGroup.Group("/login-logs")
{
loginLogs.GET("/paging", LoginLogApi.LoginLogPagingEndpoint)
loginLogs.DELETE("/:id", LoginLogApi.LoginLogDeleteEndpoint)
loginLogs.POST("/clear", LoginLogApi.LoginLogClearEndpoint)
}
storageLogs := adminGroup.Group("/storage-logs")
{
storageLogs.GET("/paging", StorageLogApi.PagingEndpoint)
storageLogs.DELETE("/:id", StorageLogApi.DeleteEndpoint)
storageLogs.POST("/clear", StorageLogApi.ClearEndpoint)
}
adminGroup.GET("/properties", PropertyApi.PropertyGetEndpoint)
adminGroup.PUT("/properties", PropertyApi.PropertyUpdateEndpoint)
adminGroup.POST("/properties/gen-rsa-private-key", PropertyApi.GenRSAPrivateKeyEndpoint)
adminGroup.POST("/properties/send-mail", PropertyApi.SendMailEndpoint)
adminGroup.GET("/properties/client-ips", PropertyApi.ClientIPsEndpoint)
adminGroup.GET("/logos", LogoApi.LogosEndpoint)
adminGroup.POST("/logos/upload", LogoApi.UploadEndpoint)
adminGroup.DELETE("/logos/:name", LogoApi.DeleteEndpoint)
adminGroup.GET("/overview/counter", OverviewApi.OverviewCounterEndPoint)
adminGroup.GET("/overview/asset", OverviewApi.OverviewAssetEndPoint)
adminGroup.GET("/overview/date-counter", OverviewApi.OverviewDateCounterEndPoint)
adminGroup.GET("/overview/ps", OverviewApi.OverviewPS)
jobs := adminGroup.Group("/jobs")
{
jobs.POST("", JobApi.JobCreateEndpoint)
jobs.GET("/paging", JobApi.JobPagingEndpoint)
jobs.PUT("/:id", JobApi.JobUpdateEndpoint)
jobs.POST("/:id/change-status", JobApi.JobChangeStatusEndpoint)
jobs.POST("/:id/exec", JobApi.JobExecEndpoint)
jobs.DELETE("/:id", JobApi.JobDeleteEndpoint)
jobs.GET("/:id", JobApi.JobGetEndpoint)
jobs.GET("/:id/logs/paging", JobApi.JobGetLogsEndpoint)
jobs.DELETE("/:id/logs", JobApi.JobDeleteLogsEndpoint)
}
securities := adminGroup.Group("/securities")
{
securities.POST("", SecurityApi.SecurityCreateEndpoint)
securities.GET("/paging", SecurityApi.SecurityPagingEndpoint)
securities.PUT("/:id", SecurityApi.SecurityUpdateEndpoint)
securities.DELETE("/:id", SecurityApi.SecurityDeleteEndpoint)
securities.GET("/:id", SecurityApi.SecurityGetEndpoint)
}
storages := adminGroup.Group("/storages")
{
storages.GET("/paging", StorageApi.StoragePagingEndpoint)
storages.POST("", StorageApi.StorageCreateEndpoint)
storages.DELETE("/:id", StorageApi.StorageDeleteEndpoint)
storages.PUT("/:id", StorageApi.StorageUpdateEndpoint)
storages.GET("/shares", StorageApi.StorageSharesEndpoint)
storages.GET("/:id", StorageApi.StorageGetEndpoint)
storages.POST("/:storageId/ls", StorageApi.StorageLsEndpoint)
storages.GET("/:storageId/download", StorageApi.StorageDownloadEndpoint)
storages.POST("/:storageId/upload", StorageApi.StorageUploadEndpoint)
storages.POST("/:storageId/mkdir", StorageApi.StorageMkDirEndpoint)
storages.POST("/:storageId/rm", StorageApi.StorageRmEndpoint)
storages.POST("/:storageId/rename", StorageApi.StorageRenameEndpoint)
storages.POST("/:storageId/edit", StorageApi.StorageEditEndpoint)
}
strategies := adminGroup.Group("/strategies")
{
strategies.GET("", StrategyApi.StrategyAllEndpoint)
strategies.GET("/paging", StrategyApi.StrategyPagingEndpoint)
strategies.POST("", StrategyApi.StrategyCreateEndpoint)
strategies.DELETE("/:id", StrategyApi.StrategyDeleteEndpoint)
strategies.PUT("/:id", StrategyApi.StrategyUpdateEndpoint)
strategies.GET("/:id", StrategyApi.GetEndpoint)
}
accessGateways := adminGroup.Group("/access-gateways")
{
accessGateways.GET("", AccessGatewayApi.AccessGatewayAllEndpoint)
accessGateways.POST("", AccessGatewayApi.AccessGatewayCreateEndpoint)
accessGateways.GET("/paging", AccessGatewayApi.AccessGatewayPagingEndpoint)
accessGateways.PUT("/:id", AccessGatewayApi.AccessGatewayUpdateEndpoint)
accessGateways.DELETE("/:id", AccessGatewayApi.AccessGatewayDeleteEndpoint)
accessGateways.GET("/:id", AccessGatewayApi.AccessGatewayGetEndpoint)
}
agentGateways := adminGroup.Group("/agent-gateways")
{
agentGateways.GET("", AgentGatewayApi.AllEndpoint)
agentGateways.POST("", AgentGatewayApi.CreateEndpoint)
agentGateways.GET("/paging", AgentGatewayApi.PagingEndpoint)
agentGateways.PUT("/:id", AgentGatewayApi.UpdateEndpoint)
agentGateways.DELETE("/:id", AgentGatewayApi.DeleteEndpoint)
agentGateways.GET("/:id", AgentGatewayApi.GetEndpoint)
agentGateways.GET("/get-register-param", AgentGatewayApi.GetRegisterParamEndpoint)
agentGateways.POST("/set-register-addr", AgentGatewayApi.SetRegisterAddrEndpoint)
agentGateways.GET("/:id/stat", AgentGatewayApi.GetStatEndpoint)
agentGateways.POST("/sort", AgentGatewayApi.UpdateSortEndpoint)
}
adminGroup.GET("/agent/version", AgentGatewayApi.VersionEndpoint)
sshGateways := adminGroup.Group("/ssh-gateways")
{
sshGateways.GET("", SshGatewayApi.AllEndpoint)
sshGateways.POST("", SshGatewayApi.CreateEndpoint)
sshGateways.GET("/paging", SshGatewayApi.PagingEndpoint)
sshGateways.PUT("/:id", SshGatewayApi.UpdateEndpoint)
sshGateways.DELETE("/:id", SshGatewayApi.DeleteEndpoint)
sshGateways.GET("/:id", SshGatewayApi.GetEndpoint)
sshGateways.GET("/:id/decrypted", SshGatewayApi.DecryptedEndpoint)
}
adminGroup.GET("/assets/ssh/available-for-gateway", SshGatewayApi.AvailableForGatewayEndpoint)
gatewayGroups := adminGroup.Group("/gateway-groups")
{
gatewayGroups.GET("", GatewayGroupApi.AllEndpoint)
gatewayGroups.POST("", GatewayGroupApi.CreateEndpoint)
gatewayGroups.GET("/paging", GatewayGroupApi.PagingEndpoint)
gatewayGroups.PUT("/:id", GatewayGroupApi.UpdateEndpoint)
gatewayGroups.DELETE("/:id", GatewayGroupApi.DeleteEndpoint)
gatewayGroups.GET("/:id", GatewayGroupApi.GetEndpoint)
}
websites := adminGroup.Group("/websites")
{
websites.GET("", WebsiteApi.AllEndpoint)
websites.POST("", WebsiteApi.CreateEndpoint)
websites.GET("/paging", WebsiteApi.PagingEndpoint)
websites.PUT("/:id", WebsiteApi.UpdateEndpoint)
websites.DELETE("/:id", WebsiteApi.DeleteEndpoint)
websites.GET("/:id", WebsiteApi.GetEndpoint)
websites.GET("/groups", WebsiteApi.GroupsGetEndpoint)
websites.PUT("/groups", WebsiteApi.GroupsSetEndpoint)
websites.DELETE("/groups/:id", WebsiteApi.GroupsDeleteEndpoint)
websites.POST("/change-group", WebsiteApi.ChangeGroupEndpoint)
websites.POST("/change-gateway", WebsiteApi.ChangeGatewayEndpoint)
websites.POST("/sort", WebsiteApi.SortEndpoint)
websites.GET("/favicon", WebsiteApi.FaviconEndpoint)
}
certificates := adminGroup.Group("/certificates")
{
certificates.GET("", CertificateApi.AllEndpoint)
certificates.POST("", CertificateApi.CreateEndpoint)
certificates.GET("/paging", CertificateApi.PagingEndpoint)
certificates.PUT("/:id", CertificateApi.UpdateEndpoint)
certificates.DELETE("/:id", CertificateApi.DeleteEndpoint)
certificates.GET("/:id", CertificateApi.GetEndpoint)
certificates.PATCH("/:id/default", CertificateApi.UpdateDefaultEndpoint)
certificates.GET("/:id/download", CertificateApi.DownloadEndpoint)
certificates.POST("/:id/renew", CertificateApi.RenewEndpoint)
}
backup := adminGroup.Group("/backup")
{
backup.GET("/export", BackupApi.BackupExportEndpoint)
backup.POST("/import", BackupApi.BackupImportEndpoint)
}
tenants := adminGroup.Group("/tenants")
{
tenants.GET("", TenantApi.AllEndpoint)
tenants.GET("/paging", TenantApi.PagingEndpoint)
tenants.POST("", TenantApi.CreateEndpoint)
tenants.DELETE("/:id", TenantApi.DeleteEndpoint)
tenants.PUT("/:id", TenantApi.UpdateEndpoint)
}
roles := adminGroup.Group("/roles")
{
roles.GET("", RoleApi.AllEndpoint)
roles.GET("/paging", RoleApi.PagingEndpoint)
roles.GET("/:id", RoleApi.GetEndpoint)
roles.POST("", RoleApi.CreateEndpoint)
roles.DELETE("/:id", RoleApi.DeleteEndpoint)
roles.PUT("/:id", RoleApi.UpdateEndpoint)
}
loginPolicies := adminGroup.Group("/login-policies")
{
loginPolicies.GET("/paging", LoginPolicyApi.PagingEndpoint)
loginPolicies.GET("/:id", LoginPolicyApi.GetEndpoint)
loginPolicies.GET("/:id/users/paging", LoginPolicyApi.GetUserPageEndpoint)
loginPolicies.GET("/:id/users/id", LoginPolicyApi.GetUserIdEndpoint)
loginPolicies.POST("", LoginPolicyApi.CreateEndpoint)
loginPolicies.DELETE("/:id", LoginPolicyApi.DeleteEndpoint)
loginPolicies.PUT("/:id", LoginPolicyApi.UpdateEndpoint)
loginPolicies.POST("/:id/bind", LoginPolicyApi.BindEndpoint)
loginPolicies.POST("/:id/unbind", LoginPolicyApi.UnbindEndpoint)
}
authorised := adminGroup.Group("/authorised")
{
authorised.GET("/assets/paging", AuthorisedApi.PagingAsset)
authorised.GET("/users/paging", AuthorisedApi.PagingUser)
authorised.GET("/user-groups/paging", AuthorisedApi.PagingUserGroup)
authorised.GET("/selected", AuthorisedApi.Selected)
authorised.POST("/assets", AuthorisedApi.AuthorisedAssets)
authorised.POST("/users", AuthorisedApi.AuthorisedUsers)
authorised.POST("/user-groups", AuthorisedApi.AuthorisedUserGroups)
authorised.DELETE("/:id", AuthorisedApi.Delete)
}
adminGroup.GET("/menus", RoleApi.TreeMenus)
ScheduledTaskApi := new(api.ScheduledTaskApi)
scheduledTasks := adminGroup.Group("/scheduled-tasks")
{
scheduledTasks.GET("", ScheduledTaskApi.AllEndpoint)
scheduledTasks.GET("/paging", ScheduledTaskApi.PagingEndpoint)
scheduledTasks.POST("", ScheduledTaskApi.CreateEndpoint)
scheduledTasks.PUT("/:id", ScheduledTaskApi.UpdateEndpoint)
scheduledTasks.DELETE("/:id", ScheduledTaskApi.DeleteEndpoint)
scheduledTasks.GET("/:id", ScheduledTaskApi.GetEndpoint)
scheduledTasks.POST("/:id/change-status", ScheduledTaskApi.ChangeStatusEndpoint)
scheduledTasks.POST("/:id/exec", ScheduledTaskApi.ExecEndpoint)
scheduledTasks.GET("/:id/logs/paging", ScheduledTaskApi.GetLogsEndpoint)
scheduledTasks.DELETE("/:id/logs", ScheduledTaskApi.DeleteLogsEndpoint)
scheduledTasks.POST("/next-ten-runs", ScheduledTaskApi.NextTenRunsEndpoint)
}
SessionCommandApi := new(api.SessionCommandApi)
sessionCommands := adminGroup.Group("/session-commands")
{
sessionCommands.GET("", SessionCommandApi.AllEndpoint)
sessionCommands.GET("/paging", SessionCommandApi.PagingEndpoint)
sessionCommands.GET("/:id", SessionCommandApi.GetEndpoint)
}
OperationLogApi := new(api.OperationLogApi)
operationLogs := adminGroup.Group("/operation-logs")
{
operationLogs.GET("", OperationLogApi.AllEndpoint)
operationLogs.GET("/paging", OperationLogApi.PagingEndpoint)
operationLogs.DELETE("/:id", OperationLogApi.DeleteEndpoint)
operationLogs.POST("/clear", OperationLogApi.ClearEndpoint)
}
OidcClientApi := new(api.OidcClientApi)
oidcClients := adminGroup.Group("/oidc-clients")
{
oidcClients.GET("", OidcClientApi.AllEndpoint)
oidcClients.GET("/paging", OidcClientApi.PagingEndpoint)
oidcClients.POST("", OidcClientApi.CreateEndpoint)
oidcClients.PUT("/:id", OidcClientApi.UpdateEndpoint)
oidcClients.DELETE("/:id", OidcClientApi.DeleteEndpoint)
oidcClients.GET("/:id", OidcClientApi.GetEndpoint)
oidcClients.POST("/:id/regenerate-secret", OidcClientApi.RegenerateSecretEndpoint)
oidcClients.PATCH("/:id/status", OidcClientApi.UpdateStatusEndpoint)
}
LoginLockedApi := new(api.LoginLockedApi)
loginLocked := adminGroup.Group("/login-locked")
{
loginLocked.GET("", LoginLockedApi.AllEndpoint)
loginLocked.GET("/paging", LoginLockedApi.PagingEndpoint)
loginLocked.DELETE("/:id", LoginLockedApi.DeleteEndpoint)
}
FilesystemLogApi := new(api.FilesystemLogApi)
filesystemLogs := adminGroup.Group("/filesystem-logs")
{
filesystemLogs.GET("", FilesystemLogApi.AllEndpoint)
filesystemLogs.GET("/paging", FilesystemLogApi.PagingEndpoint)
filesystemLogs.DELETE("/:id", FilesystemLogApi.DeleteEndpoint)
filesystemLogs.POST("/clear", FilesystemLogApi.ClearEndpoint)
}
CommandFilterRuleApi := new(api.CommandFilterRuleApi)
commandFilterRules := adminGroup.Group("/command-filter-rules")
{
commandFilterRules.GET("", CommandFilterRuleApi.AllEndpoint)
commandFilterRules.GET("/paging", CommandFilterRuleApi.PagingEndpoint)
commandFilterRules.POST("", CommandFilterRuleApi.CreateEndpoint)
commandFilterRules.PUT("/:id", CommandFilterRuleApi.UpdateEndpoint)
commandFilterRules.DELETE("/:id", CommandFilterRuleApi.DeleteEndpoint)
commandFilterRules.GET("/:id", CommandFilterRuleApi.GetEndpoint)
}
AgentGatewayTokenApi := new(api.AgentGatewayTokenApi)
agentGatewayTokens := adminGroup.Group("/agent-gateway-tokens")
{
agentGatewayTokens.GET("", AgentGatewayTokenApi.AllEndpoint)
agentGatewayTokens.GET("/paging", AgentGatewayTokenApi.PagingEndpoint)
agentGatewayTokens.POST("", AgentGatewayTokenApi.CreateEndpoint)
agentGatewayTokens.PUT("/:id", AgentGatewayTokenApi.UpdateEndpoint)
agentGatewayTokens.DELETE("/:id", AgentGatewayTokenApi.DeleteEndpoint)
agentGatewayTokens.GET("/:id", AgentGatewayTokenApi.GetEndpoint)
}
AccessLogApi := new(api.AccessLogApi)
accessLogs := adminGroup.Group("/access-logs")
{
accessLogs.GET("", AccessLogApi.AllEndpoint)
accessLogs.GET("/paging", AccessLogApi.PagingEndpoint)
accessLogs.DELETE("/:id", AccessLogApi.DeleteEndpoint)
accessLogs.POST("/clear", AccessLogApi.ClearEndpoint)
accessLogs.GET("/domain-stats", AccessLogApi.GetDomainStatsEndpoint)
accessLogs.GET("/daily-stats", AccessLogApi.GetDailyStatsEndpoint)
accessLogs.GET("/status-code-stats", AccessLogApi.GetStatusCodeStatsEndpoint)
accessLogs.GET("/hourly-stats", AccessLogApi.GetHourlyStatsEndpoint)
accessLogs.GET("/total-stats", AccessLogApi.GetTotalStatsEndpoint)
accessLogs.GET("/website-stats", AccessLogApi.GetWebsiteStatsEndpoint)
accessLogs.GET("/website-traffic-trend", AccessLogApi.GetWebsiteTrafficTrendEndpoint)
accessLogs.GET("/top-pages", AccessLogApi.GetTopPagesEndpoint)
accessLogs.GET("/top-referers", AccessLogApi.GetTopReferersEndpoint)
accessLogs.GET("/realtime-metrics", AccessLogApi.GetRealtimeMetricsEndpoint)
accessLogs.GET("/website-hourly-stats", AccessLogApi.GetWebsiteHourlyStatsEndpoint)
accessLogs.GET("/website-status-code-stats", AccessLogApi.GetWebsiteStatusCodeStatsEndpoint)
}
AuthorisedAssetApi := new(api.AuthorisedAssetApi)
authorisedAsset := adminGroup.Group("/authorised-asset")
{
authorisedAsset.GET("/paging", AuthorisedAssetApi.PagingEndpoint)
authorisedAsset.POST("/assets", AuthorisedAssetApi.AuthorisedAssetsEndpoint)
authorisedAsset.POST("/users", AuthorisedAssetApi.AuthorisedUsersEndpoint)
authorisedAsset.POST("/departments", AuthorisedAssetApi.AuthorisedDepartmentsEndpoint)
authorisedAsset.GET("/selected", AuthorisedAssetApi.SelectedEndpoint)
authorisedAsset.DELETE("/:id", AuthorisedAssetApi.DeleteEndpoint)
authorisedAsset.GET("/:id", AuthorisedAssetApi.GetEndpoint)
authorisedAsset.PUT("/:id", AuthorisedAssetApi.UpdateEndpoint)
authorisedAsset.POST("", AuthorisedAssetApi.CreateEndpoint)
}
AuthorisedDatabaseAssetApi := new(api.AuthorisedDatabaseAssetApi)
authorisedDatabaseAssets := adminGroup.Group("/authorised-database-assets")
{
authorisedDatabaseAssets.GET("/paging", AuthorisedDatabaseAssetApi.PagingEndpoint)
authorisedDatabaseAssets.GET("/selected", AuthorisedDatabaseAssetApi.SelectedEndpoint)
authorisedDatabaseAssets.DELETE("/:id", AuthorisedDatabaseAssetApi.DeleteEndpoint)
authorisedDatabaseAssets.GET("/:id", AuthorisedDatabaseAssetApi.GetEndpoint)
authorisedDatabaseAssets.PUT("/:id", AuthorisedDatabaseAssetApi.UpdateEndpoint)
authorisedDatabaseAssets.POST("", AuthorisedDatabaseAssetApi.CreateEndpoint)
}
AuthorisedWebsiteApi := new(api.AuthorisedWebsiteApi)
authorisedWebsite := adminGroup.Group("/authorised-website")
{
authorisedWebsite.GET("/paging", AuthorisedWebsiteApi.PagingEndpoint)
authorisedWebsite.DELETE("/:id", AuthorisedWebsiteApi.DeleteEndpoint)
authorisedWebsite.POST("", AuthorisedWebsiteApi.CreateEndpoint)
}
DbWorkOrderApi := new(api.DbWorkOrderApi)
dbWorkOrders := adminGroup.Group("/db-work-orders")
{
dbWorkOrders.GET("/paging", DbWorkOrderApi.PagingEndpoint)
dbWorkOrders.GET("/:id", DbWorkOrderApi.GetEndpoint)
dbWorkOrders.POST("", DbWorkOrderApi.CreateEndpoint)
dbWorkOrders.POST("/:id/approve", DbWorkOrderApi.ApproveEndpoint)
dbWorkOrders.POST("/:id/reject", DbWorkOrderApi.RejectEndpoint)
dbWorkOrders.DELETE("/:id", DbWorkOrderApi.DeleteEndpoint)
}
DatabaseSQLLogApi := new(api.DatabaseSQLLogApi)
databaseSqlLogs := adminGroup.Group("/database-sql-logs")
{
databaseSqlLogs.GET("/paging", DatabaseSQLLogApi.PagingEndpoint)
databaseSqlLogs.POST("/clear", DatabaseSQLLogApi.ClearEndpoint)
}
DnsProviderApi := new(api.DnsProviderApi)
dnsProviders := adminGroup.Group("/dns-providers")
{
dnsProviders.GET("/config", DnsProviderApi.GetConfigEndpoint)
dnsProviders.PUT("/config", DnsProviderApi.SetConfigEndpoint)
dnsProviders.DELETE("/config", DnsProviderApi.DeleteConfigEndpoint)
}
LicenseApi := new(api.LicenseApi)
adminGroup.GET("/license/machine-id", LicenseApi.GetMachineIdEndpoint)
adminGroup.GET("/license", LicenseApi.GetLicenseEndpoint)
adminGroup.POST("/license", LicenseApi.SetLicenseEndpoint)
adminGroup.POST("/license/request", LicenseApi.RequestLicenseEndpoint)
apiGroup.GET("/license", LicenseApi.GetSimpleLicenseEndpoint)
WebsiteTempAllowApi := new(api.WebsiteTempAllowApi)
adminGroup.GET("/website-temp-allow", WebsiteTempAllowApi.ListEndpoint)
adminGroup.DELETE("/website-temp-allow", WebsiteTempAllowApi.DeleteEndpoint)
}
portalGroup := apiGroup.Group("/portal")
{
portalGroup.GET("/assets", PortalApi.AssetsEndpoint)
portalGroup.GET("/database-assets", PortalApi.DatabaseAssetsEndpoint)
portalGroup.GET("/assets/tree", PortalApi.AssetsTreeEndpoint)
portalGroup.GET("/websites/tree", PortalApi.WebsitesTreeEndpoint)
portalGroup.GET("/assets/group-tree", PortalApi.AssetsGroupTreeEndpoint)
portalGroup.GET("/websites/group-tree", PortalApi.WebsitesGroupTreeEndpoint)
portalGroup.GET("/access-require-mfa", PortalApi.AccessRequireMfaEndpoint)
portalGroup.POST("/sessions", PortalApi.CreateSessionEndpoint)
portalGroup.GET("/sessions/:id", PortalApi.GetSessionEndpoint)
portalGroup.GET("/sessions/:id/share", PortalApi.GetShareEndpoint)
portalGroup.POST("/sessions/:id/share", PortalApi.CreateShareEndpoint)
portalGroup.DELETE("/sessions/:id/share", PortalApi.CancelShareEndpoint)
portalGroup.GET("/website/access", PortalApi.AccessWebsiteEndpoint)
portalGroup.POST("/website/allow-ip", PortalApi.AllowWebsiteIpEndpoint)
portalGroup.POST("/assets/:id/wol", PortalApi.WakeOnLanEndpoint)
portalGroup.POST("/assets/:id/ping", PortalApi.PingAssetEndpoint)
SnippetUserApi := new(api.SnippetUserApi)
portalGroup.GET("/snippets", SnippetUserApi.AllEndpoint)
portalGroup.GET("/snippets/paging", SnippetUserApi.PagingEndpoint)
portalGroup.GET("/snippets/:id", SnippetUserApi.GetEndpoint)
}
DbWorkOrderUserApi := new(api.DbWorkOrderApi)
apiGroup.GET("/db-work-orders/paging", DbWorkOrderUserApi.PagingEndpoint)
apiGroup.GET("/db-work-orders/:id", DbWorkOrderUserApi.GetEndpoint)
apiGroup.POST("/db-work-orders", DbWorkOrderUserApi.CreateEndpoint)
OidcApi := new(api.OidcApi)
apiGroup.GET("/oidc/authorize", OidcApi.AuthorizeEndpoint)
apiGroup.POST("/oidc/login", OidcApi.LoginEndpoint)
WechatWorkApi := new(api.WechatWorkApi)
apiGroup.GET("/wechat-work/authorize", WechatWorkApi.AuthorizeEndpoint)
apiGroup.POST("/wechat-work/login", WechatWorkApi.LoginEndpoint)
apiGroup.GET("/access/terminal/:id/stats", PortalApi.TerminalStatsEndpoint)
apiGroup.GET("/access/settings", AccessSettingApi.GetEndpoint)
apiGroup.PUT("/access/settings", AccessSettingApi.SetEndpoint)
apiGroup.GET("/access/settings/shell-assistant-enabled", AccessSettingApi.ShellAssistantEnabledEndpoint)
apiGroup.POST("/access/settings/shell-assistant", AccessSettingApi.ShellAssistantEndpoint)
apiGroup.GET("/access/terminal", webTerminalApi.AccessTerminalEndpoint)
filesystem := apiGroup.Group("/access/filesystem")
{
filesystem.GET("/:id/ls", FileSystemApi.LsEndpoint)
filesystem.POST("/:id/mkdir", FileSystemApi.MkdirEndpoint)
filesystem.POST("/:id/touch", FileSystemApi.TouchEndpoint)
filesystem.POST("/:id/rm", FileSystemApi.RmEndpoint)
filesystem.POST("/:id/rename", FileSystemApi.RenameEndpoint)
filesystem.POST("/:id/edit", FileSystemApi.EditEndpoint)
filesystem.GET("/:id/read", FileSystemApi.ReadEndpoint)
filesystem.POST("/:id/chmod", FileSystemApi.ChmodEndpoint)
filesystem.GET("/:id/upload/progress", FileSystemApi.UploadProgressEndpoint)
filesystem.GET("/:id/download", FileSystemApi.DownloadEndpoint)
filesystem.POST("/:id/upload", FileSystemApi.UploadEndpoint)
}
}
return e
}
+18
View File
@@ -0,0 +1,18 @@
package branding
import (
"fmt"
"time"
)
var Name = "Next Terminal"
var Copyright = fmt.Sprintf("Copyright © 2020-%d dushixiang, All Rights Reserved.", time.Now().Year())
var Banner = ` ___ ___
/\__\ /\ \
/:| _|_ \:\ \
/::|/\__\ /::\__\
\/|::/ / /:/\/__/
|:/ / \/__/
\/__/ `
var Version = `v1.3.9`
var Hi = Banner + Version
+308
View File
@@ -0,0 +1,308 @@
package guacamole
import (
"bufio"
"encoding/base64"
"errors"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
)
const (
EnableRecording = "enable-recording"
RecordingPath = "recording-path"
CreateRecordingPath = "create-recording-path"
FontName = "font-name"
FontSize = "font-size"
ColorScheme = "color-scheme"
Backspace = "backspace"
TerminalType = "terminal-type"
PreConnectionId = "preconnection-id"
PreConnectionBlob = "preconnection-blob"
EnableDrive = "enable-drive"
DriveName = "drive-name"
DrivePath = "drive-path"
EnableWallpaper = "enable-wallpaper"
EnableTheming = "enable-theming"
EnableFontSmoothing = "enable-font-smoothing"
EnableFullWindowDrag = "enable-full-window-drag"
EnableDesktopComposition = "enable-desktop-composition"
EnableMenuAnimations = "enable-menu-animations"
DisableBitmapCaching = "disable-bitmap-caching"
DisableOffscreenCaching = "disable-offscreen-caching"
// DisableGlyphCaching Deprecated
DisableGlyphCaching = "disable-glyph-caching"
ForceLossless = "force-lossless"
Domain = "domain"
RemoteApp = "remote-app"
RemoteAppDir = "remote-app-dir"
RemoteAppArgs = "remote-app-args"
ColorDepth = "color-depth"
Cursor = "cursor"
SwapRedBlue = "swap-red-blue"
DestHost = "dest-host"
DestPort = "dest-port"
ReadOnly = "read-only"
UsernameRegex = "username-regex"
PasswordRegex = "password-regex"
LoginSuccessRegex = "login-success-regex"
LoginFailureRegex = "login-failure-regex"
Namespace = "namespace"
Pod = "pod"
Container = "container"
UesSSL = "use-ssl"
ClientCert = "client-cert"
ClientKey = "client-key"
CaCert = "ca-cert"
IgnoreCert = "ignore-cert"
)
const Delimiter = ';'
const Version = "VERSION_1_4_0"
type Configuration struct {
ConnectionID string
Protocol string
Parameters map[string]string
}
func NewConfiguration() (config *Configuration) {
config = &Configuration{}
config.Parameters = make(map[string]string)
return config
}
func (opt *Configuration) SetReadOnlyMode() {
opt.Parameters[ReadOnly] = "true"
}
func (opt *Configuration) SetParameter(name, value string) {
opt.Parameters[name] = value
}
func (opt *Configuration) UnSetParameter(name string) {
delete(opt.Parameters, name)
}
func (opt *Configuration) GetParameter(name string) string {
return opt.Parameters[name]
}
type Instruction struct {
Opcode string
Args []string
ProtocolForm string
}
func NewInstruction(opcode string, args ...string) (ret Instruction) {
ret.Opcode = opcode
ret.Args = args
return ret
}
func (opt *Instruction) String() string {
if len(opt.ProtocolForm) > 0 {
return opt.ProtocolForm
}
opt.ProtocolForm = fmt.Sprintf("%d.%s", len(opt.Opcode), opt.Opcode)
for _, value := range opt.Args {
opt.ProtocolForm += fmt.Sprintf(",%d.%s", len(value), value)
}
opt.ProtocolForm += string(Delimiter)
return opt.ProtocolForm
}
func (opt *Instruction) Parse(content string) Instruction {
if strings.LastIndex(content, ";") > 0 {
content = strings.TrimRight(content, ";")
}
messages := strings.Split(content, ",")
var args = make([]string, len(messages))
for i := range messages {
lm := strings.Split(messages[i], ".")
args[i] = lm[1]
}
return NewInstruction(args[0], args[1:]...)
}
type Tunnel struct {
conn net.Conn
reader *bufio.Reader
writer *bufio.Writer
UUID string
Config *Configuration
IsOpen bool
}
func NewTunnel(address string, config *Configuration) (ret *Tunnel, err error) {
conn, err := net.DialTimeout("tcp", address, 5*time.Second)
if err != nil {
return
}
ret = &Tunnel{}
ret.conn = conn
ret.reader = bufio.NewReader(conn)
ret.writer = bufio.NewWriter(conn)
ret.Config = config
selectArg := config.ConnectionID
if selectArg == "" {
selectArg = config.Protocol
}
if err := ret.WriteInstructionAndFlush(NewInstruction("select", selectArg)); err != nil {
_ = conn.Close()
return nil, err
}
args, err := ret.expect("args")
if err != nil {
_ = conn.Close()
return
}
width := config.GetParameter("width")
height := config.GetParameter("height")
dpi := config.GetParameter("dpi")
// send size
if err := ret.WriteInstructionAndFlush(NewInstruction("size", width, height, dpi)); err != nil {
_ = conn.Close()
return nil, err
}
if err := ret.WriteInstructionAndFlush(NewInstruction("audio", "audio/L8", "audio/L16")); err != nil {
_ = conn.Close()
return nil, err
}
if err := ret.WriteInstructionAndFlush(NewInstruction("video")); err != nil {
_ = conn.Close()
return nil, err
}
if err := ret.WriteInstructionAndFlush(NewInstruction("image", "image/jpeg", "image/png", "image/webp")); err != nil {
_ = conn.Close()
return nil, err
}
if err := ret.WriteInstructionAndFlush(NewInstruction("timezone", "Asia/Shanghai")); err != nil {
_ = conn.Close()
return nil, err
}
parameters := make([]string, len(args.Args))
for i := range args.Args {
argName := args.Args[i]
if strings.Contains(argName, "VERSION") {
parameters[i] = Version
continue
}
parameters[i] = config.GetParameter(argName)
}
// send connect
if err := ret.WriteInstructionAndFlush(NewInstruction("connect", parameters...)); err != nil {
_ = conn.Close()
return nil, err
}
ready, err := ret.expect("ready")
if err != nil {
return
}
if len(ready.Args) == 0 {
_ = conn.Close()
return nil, errors.New("no connection id received")
}
ret.UUID = ready.Args[0]
ret.IsOpen = true
return ret, nil
}
func (opt *Tunnel) WriteInstructionAndFlush(instruction Instruction) error {
if _, err := opt.WriteAndFlush([]byte(instruction.String())); err != nil {
return err
}
return nil
}
func (opt *Tunnel) WriteAndFlush(p []byte) (int, error) {
//fmt.Printf("-> %v\n", string(p))
nn, err := opt.writer.Write(p)
if err != nil {
return nn, err
}
err = opt.writer.Flush()
if err != nil {
return nn, err
}
return nn, nil
}
func (opt *Tunnel) ReadInstruction() (instruction Instruction, err error) {
msg, err := opt.Read()
if err != nil {
return instruction, err
}
return instruction.Parse(string(msg)), err
}
func (opt *Tunnel) Read() (p []byte, err error) {
data, err := opt.reader.ReadBytes(Delimiter)
if err != nil {
return
}
s := string(data)
//fmt.Printf("<- %v \n", s)
if s == "rate=44100,channels=2;" {
return make([]byte, 0), nil
}
if s == "rate=22050,channels=2;" {
return make([]byte, 0), nil
}
if s == "5.audio,1.1,31.audio/L16;" {
s += "rate=44100,channels=2;"
}
return []byte(s), err
}
func (opt *Tunnel) expect(opcode string) (instruction Instruction, err error) {
instruction, err = opt.ReadInstruction()
if err != nil {
return instruction, err
}
if opcode != instruction.Opcode {
msg := fmt.Sprintf(`expected "%s" instruction but instead received "%s"`, opcode, instruction.Opcode)
return instruction, errors.New(msg)
}
return instruction, nil
}
func (opt *Tunnel) Close() error {
opt.IsOpen = false
return opt.conn.Close()
}
func Disconnect(ws *websocket.Conn, code int, reason string) {
// guacd 无法处理中文字符,所以进行了base64编码。
encodeReason := base64.StdEncoding.EncodeToString([]byte(reason))
err := NewInstruction("error", encodeReason, strconv.Itoa(code))
_ = ws.WriteMessage(websocket.TextMessage, []byte(err.String()))
disconnect := NewInstruction("disconnect")
_ = ws.WriteMessage(websocket.TextMessage, []byte(disconnect.String()))
}
+56
View File
@@ -0,0 +1,56 @@
package common
import (
"database/sql/driver"
"fmt"
"strings"
"time"
)
type JsonTime struct {
time.Time
}
func NewJsonTime(t time.Time) JsonTime {
return JsonTime{
Time: t,
}
}
func NowJsonTime() JsonTime {
return JsonTime{
Time: time.Now(),
}
}
func (j JsonTime) MarshalJSON() ([]byte, error) {
var stamp = fmt.Sprintf("\"%s\"", j.Format("2006-01-02 15:04:05"))
return []byte(stamp), nil
}
func (j *JsonTime) UnmarshalJSON(b []byte) error {
s := strings.ReplaceAll(string(b), "\"", "")
t, err := time.Parse("2006-01-02 15:04:05", s)
if err != nil {
return err
}
*j = NewJsonTime(t)
return nil
}
func (j JsonTime) Value() (driver.Value, error) {
var zeroTime time.Time
if j.Time.UnixNano() == zeroTime.UnixNano() {
return nil, nil
}
return j.Time, nil
}
func (j *JsonTime) Scan(v interface{}) error {
value, ok := v.(time.Time)
if ok {
*j = JsonTime{Time: value}
return nil
}
return fmt.Errorf("can not convert %v to timestamp", v)
}
+3
View File
@@ -0,0 +1,3 @@
package maps
type Map map[string]interface{}
+80
View File
@@ -0,0 +1,80 @@
package nt
import (
"next-terminal/server/common/guacamole"
)
const Token = "X-Auth-Token"
type Key string
const (
DB Key = "db"
SSH = "ssh"
RDP = "rdp"
VNC = "vnc"
Telnet = "telnet"
K8s = "kubernetes"
AccessRuleAllow = "allow" // 允许访问
AccessRuleReject = "reject" // 拒绝访问
Custom = "custom" // 密码
PrivateKey = "private-key" // 密钥
JobStatusRunning = "running" // 计划任务运行状态
JobStatusNotRunning = "not-running" // 计划任务未运行状态
FuncCheckAssetStatusJob = "check-asset-status-job" // 检测资产是否在线
FuncShellJob = "shell-job" // 执行Shell脚本
JobModeSelf = "self" // 本机
JobModeAll = "all" // 全部资产
JobModeCustom = "custom" // 自定义选择资产
SshMode = "ssh-mode" // ssh模式
MailHost = "mail-host" // 邮件服务器地址
MailPort = "mail-port" // 邮件服务器端口
MailUsername = "mail-username" // 邮件服务账号
MailPassword = "mail-password" // 邮件服务密码
NoConnect = "no_connect" // 会话状态:未连接
Connecting = "connecting" // 会话状态:连接中
Connected = "connected" // 会话状态:已连接
Disconnected = "disconnected" // 会话状态:已断开连接
Guacd = "guacd" // 接入模式:guacd
Native = "native" // 接入模式:原生
Terminal = "terminal" // 接入模式:终端
TypeUser = "user" // 普通用户
TypeAdmin = "admin" // 管理员
SourceLdap = "ldap" // 从LDAP同步的用户
StatusEnabled = "enabled"
StatusDisabled = "disabled"
SocksProxyEnable = "socks-proxy-enable"
SocksProxyHost = "socks-proxy-host"
SocksProxyPort = "socks-proxy-port"
SocksProxyUsername = "socks-proxy-username"
SocksProxyPassword = "socks-proxy-password"
LoginToken = "login-token"
AccessToken = "access-token"
ShareSession = "share-session"
Anonymous = "anonymous"
StorageLogActionRm = "rm" // 删除
StorageLogActionUpload = "upload" // 上传
StorageLogActionDownload = "download" // 下载
StorageLogActionMkdir = "mkdir" // 创建文件夹
StorageLogActionRename = "rename" // 重命名
)
var SSHParameterNames = []string{guacamole.FontName, guacamole.FontSize, guacamole.ColorScheme, guacamole.Backspace, guacamole.TerminalType, SshMode, SocksProxyEnable, SocksProxyHost, SocksProxyPort, SocksProxyUsername, SocksProxyPassword}
var RDPParameterNames = []string{guacamole.Domain, guacamole.RemoteApp, guacamole.RemoteAppDir, guacamole.RemoteAppArgs, guacamole.EnableDrive, guacamole.DrivePath, guacamole.ColorDepth, guacamole.ForceLossless, guacamole.PreConnectionId, guacamole.PreConnectionBlob}
var VNCParameterNames = []string{guacamole.ColorDepth, guacamole.Cursor, guacamole.SwapRedBlue, guacamole.DestHost, guacamole.DestPort}
var TelnetParameterNames = []string{guacamole.FontName, guacamole.FontSize, guacamole.ColorScheme, guacamole.Backspace, guacamole.TerminalType, guacamole.UsernameRegex, guacamole.PasswordRegex, guacamole.LoginSuccessRegex, guacamole.LoginFailureRegex}
var KubernetesParameterNames = []string{guacamole.FontName, guacamole.FontSize, guacamole.ColorScheme, guacamole.Backspace, guacamole.TerminalType, guacamole.Namespace, guacamole.Pod, guacamole.Container, guacamole.UesSSL, guacamole.ClientCert, guacamole.ClientKey, guacamole.CaCert, guacamole.IgnoreCert}
+8
View File
@@ -0,0 +1,8 @@
package nt
import "errors"
var (
ErrNameAlreadyUsed = errors.New("name already used")
ErrPermissionDenied = errors.New("permission denied")
)
+34
View File
@@ -0,0 +1,34 @@
package sets
func NewStringSet() *Set {
return &Set{data: make(map[string]struct{})}
}
type Set struct {
data map[string]struct{}
}
func (s *Set) Add(key ...string) {
for _, k := range key {
s.data[k] = struct{}{}
}
}
func (s *Set) Remove(key ...string) {
for _, k := range key {
delete(s.data, k)
}
}
func (s *Set) Contains(key string) bool {
_, ok := s.data[key]
return ok
}
func (s *Set) ToArray() []string {
var keys []string
for key, _ := range s.data {
keys = append(keys, key)
}
return keys
}
+1
View File
@@ -0,0 +1 @@
package slices
+30
View File
@@ -0,0 +1,30 @@
package taskrunner
import "sync"
type Runner struct {
wg sync.WaitGroup
errors []error
mux sync.Mutex
}
func (r *Runner) Add(f func() error) {
r.wg.Add(1)
go func() {
defer r.wg.Done()
if err := f(); err != nil {
r.addError(err)
}
}()
}
func (r *Runner) addError(err error) {
r.mux.Lock()
defer r.mux.Unlock()
r.errors = append(r.errors, err)
}
func (r *Runner) Wait() []error {
r.wg.Wait()
return r.errors
}
+121
View File
@@ -0,0 +1,121 @@
package term
import (
"bufio"
"errors"
"io"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
type NextTerminal struct {
SshClient *ssh.Client
SshSession *ssh.Session
StdinPipe io.WriteCloser
SftpClient *sftp.Client
Recorder *Recorder
StdoutReader *bufio.Reader
}
func NewNextTerminal(ip string, port int, username, password, privateKey, passphrase string, rows, cols int, recording, term string, pipe bool) (*NextTerminal, error) {
sshClient, err := NewSshClient(ip, port, username, password, privateKey, passphrase)
if err != nil {
return nil, err
}
return newNT(sshClient, pipe, recording, term, rows, cols)
}
func NewNextTerminalUseSocks(ip string, port int, username, password, privateKey, passphrase string, rows, cols int, recording, term string, pipe bool, socksProxyHost, socksProxyPort, socksProxyUsername, socksProxyPassword string) (*NextTerminal, error) {
sshClient, err := NewSshClientUseSocks(ip, port, username, password, privateKey, passphrase, socksProxyHost, socksProxyPort, socksProxyUsername, socksProxyPassword)
if err != nil {
return nil, err
}
return newNT(sshClient, pipe, recording, term, rows, cols)
}
func newNT(sshClient *ssh.Client, pipe bool, recording string, term string, rows int, cols int) (*NextTerminal, error) {
sshSession, err := sshClient.NewSession()
if err != nil {
return nil, err
}
var stdoutReader *bufio.Reader
if pipe {
stdoutPipe, err := sshSession.StdoutPipe()
if err != nil {
return nil, err
}
stdoutReader = bufio.NewReader(stdoutPipe)
}
var stdinPipe io.WriteCloser
if pipe {
stdinPipe, err = sshSession.StdinPipe()
if err != nil {
return nil, err
}
}
var recorder *Recorder
if recording != "" {
recorder, err = NewRecorder(recording, term, rows, cols)
if err != nil {
return nil, err
}
}
terminal := NextTerminal{
SshClient: sshClient,
SshSession: sshSession,
Recorder: recorder,
StdinPipe: stdinPipe,
StdoutReader: stdoutReader,
}
return &terminal, nil
}
func (ret *NextTerminal) Write(p []byte) (int, error) {
if ret.StdinPipe == nil {
return 0, errors.New("pipe is not open")
}
return ret.StdinPipe.Write(p)
}
func (ret *NextTerminal) Close() {
if ret.SftpClient != nil {
_ = ret.SftpClient.Close()
}
if ret.SshSession != nil {
_ = ret.SshSession.Close()
}
if ret.SshClient != nil {
_ = ret.SshClient.Close()
}
if ret.Recorder != nil {
ret.Recorder.Close()
}
}
func (ret *NextTerminal) WindowChange(h int, w int) error {
return ret.SshSession.WindowChange(h, w)
}
func (ret *NextTerminal) RequestPty(term string, h, w int) error {
modes := ssh.TerminalModes{
ssh.ECHO: 1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
return ret.SshSession.RequestPty(term, h, w, modes)
}
func (ret *NextTerminal) Shell() error {
return ret.SshSession.Shell()
}
+113
View File
@@ -0,0 +1,113 @@
package term
import (
"encoding/json"
"os"
"time"
"next-terminal/server/utils"
)
type Env struct {
Shell string `json:"SHELL"`
Term string `json:"TERM"`
}
type Header struct {
Title string `json:"title"`
Version int `json:"version"`
Height int `json:"height"`
Width int `json:"width"`
Env Env `json:"env"`
Timestamp int `json:"Timestamp"`
}
type Recorder struct {
File *os.File
Timestamp int
}
func (recorder *Recorder) Close() {
if recorder.File != nil {
_ = recorder.File.Close()
}
}
func (recorder *Recorder) WriteHeader(header *Header) (err error) {
var p []byte
if p, err = json.Marshal(header); err != nil {
return
}
if _, err := recorder.File.Write(p); err != nil {
return err
}
if _, err := recorder.File.Write([]byte("\n")); err != nil {
return err
}
recorder.Timestamp = header.Timestamp
return
}
func (recorder *Recorder) WriteData(data string) (err error) {
now := int(time.Now().UnixNano())
delta := float64(now-recorder.Timestamp*1000*1000*1000) / 1000 / 1000 / 1000
row := make([]interface{}, 0)
row = append(row, delta)
row = append(row, "o")
row = append(row, data)
var s []byte
if s, err = json.Marshal(row); err != nil {
return
}
if _, err := recorder.File.Write(s); err != nil {
return err
}
if _, err := recorder.File.Write([]byte("\n")); err != nil {
return err
}
return
}
func NewRecorder(recordingPath, term string, h int, w int) (recorder *Recorder, err error) {
recorder = &Recorder{}
parentDirectory := utils.GetParentDirectory(recordingPath)
if utils.FileExists(parentDirectory) {
if err := os.RemoveAll(parentDirectory); err != nil {
return nil, err
}
}
if err = os.MkdirAll(parentDirectory, 0777); err != nil {
return
}
var file *os.File
file, err = os.Create(recordingPath)
if err != nil {
return nil, err
}
recorder.File = file
header := &Header{
Title: "",
Version: 2,
Height: h,
Width: w,
Env: Env{Shell: "/bin/bash", Term: term},
Timestamp: int(time.Now().Unix()),
}
if err := recorder.WriteHeader(header); err != nil {
return nil, err
}
return recorder, nil
}
+123
View File
@@ -0,0 +1,123 @@
package term
import (
"fmt"
"net"
"time"
"golang.org/x/crypto/ssh"
"golang.org/x/net/proxy"
)
func NewSshClient(ip string, port int, username, password, privateKey, passphrase string) (*ssh.Client, error) {
var authMethod ssh.AuthMethod
if username == "-" || username == "" {
username = "root"
}
if password == "-" {
password = ""
}
if privateKey == "-" {
privateKey = ""
}
if passphrase == "-" {
passphrase = ""
}
var err error
if privateKey != "" {
var key ssh.Signer
if len(passphrase) > 0 {
key, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase))
if err != nil {
return nil, err
}
} else {
key, err = ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, err
}
}
authMethod = ssh.PublicKeys(key)
} else {
authMethod = ssh.Password(password)
}
config := &ssh.ClientConfig{
Timeout: 3 * time.Second,
User: username,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
addr := fmt.Sprintf("%s:%d", ip, port)
return ssh.Dial("tcp", addr, config)
}
func NewSshClientUseSocks(ip string, port int, username, password, privateKey, passphrase string, socksProxyHost, socksProxyPort, socksProxyUsername, socksProxyPassword string) (*ssh.Client, error) {
var authMethod ssh.AuthMethod
if username == "-" || username == "" {
username = "root"
}
if password == "-" {
password = ""
}
if privateKey == "-" {
privateKey = ""
}
if passphrase == "-" {
passphrase = ""
}
var err error
if privateKey != "" {
var key ssh.Signer
if len(passphrase) > 0 {
key, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase))
if err != nil {
return nil, err
}
} else {
key, err = ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, err
}
}
authMethod = ssh.PublicKeys(key)
} else {
authMethod = ssh.Password(password)
}
config := &ssh.ClientConfig{
Timeout: 3 * time.Second,
User: username,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
socksProxyAddr := fmt.Sprintf("%s:%s", socksProxyHost, socksProxyPort)
socks5, err := proxy.SOCKS5("tcp", socksProxyAddr,
&proxy.Auth{User: socksProxyUsername, Password: socksProxyPassword},
&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
},
)
if err != nil {
return nil, err
}
addr := fmt.Sprintf("%s:%d", ip, port)
conn, err := socks5.Dial("tcp", addr)
if err != nil {
return nil, err
}
clientConn, channels, requests, err := ssh.NewClientConn(conn, addr, config)
if err != nil {
return nil, err
}
return ssh.NewClient(clientConn, channels, requests), nil
}
+175
View File
@@ -0,0 +1,175 @@
package main
import (
"fmt"
"io"
"next-terminal/server/log"
"os"
"time"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
type SSHTerminal struct {
Session *ssh.Session
exitMsg string
stdout io.Reader
stdin io.Writer
stderr io.Reader
}
func main() {
sshConfig := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{
ssh.Password("root"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "172.16.101.32:22", sshConfig)
if err != nil {
log.Error(err)
}
defer client.Close()
err = New(client)
if err != nil {
fmt.Println(err)
}
}
func (t *SSHTerminal) updateTerminalSize() {
go func() {
// SIGWINCH is sent to the process when the window size of the terminal has
// changed.
sigwinchCh := make(chan os.Signal, 1)
//signal.Notify(sigwinchCh, syscall.SIN)
fd := int(os.Stdin.Fd())
termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil {
fmt.Println(err)
}
for {
select {
// The client updated the size of the local PTY. This change needs to occur
// on the server side PTY as well.
case sigwinch := <-sigwinchCh:
if sigwinch == nil {
return
}
currTermWidth, currTermHeight, err := terminal.GetSize(fd)
// Terminal size has not changed, don't do anything.
if currTermHeight == termHeight && currTermWidth == termWidth {
continue
}
err = t.Session.WindowChange(currTermHeight, currTermWidth)
if err != nil {
fmt.Printf("Unable to send window-change reqest: %s.", err)
continue
}
termWidth, termHeight = currTermWidth, currTermHeight
}
}
}()
}
func (t *SSHTerminal) interactiveSession() error {
defer func() {
if t.exitMsg == "" {
log.Info(os.Stdout, "the connection was closed on the remote side on ", time.Now().Format(time.RFC822))
} else {
log.Info(os.Stdout, t.exitMsg)
}
}()
fd := int(os.Stdin.Fd())
state, err := terminal.MakeRaw(fd)
if err != nil {
return err
}
defer terminal.Restore(fd, state)
termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil {
return err
}
termType := os.Getenv("TERM")
if termType == "" {
termType = "xterm-256color"
}
err = t.Session.RequestPty(termType, termHeight, termWidth, ssh.TerminalModes{})
if err != nil {
return err
}
t.updateTerminalSize()
t.stdin, err = t.Session.StdinPipe()
if err != nil {
return err
}
t.stdout, err = t.Session.StdoutPipe()
if err != nil {
return err
}
t.stderr, err = t.Session.StderrPipe()
go io.Copy(os.Stderr, t.stderr)
go io.Copy(os.Stdout, t.stdout)
go func() {
buf := make([]byte, 128)
for {
n, err := os.Stdin.Read(buf)
if err != nil {
fmt.Println(err)
return
}
if n > 0 {
_, err = t.stdin.Write(buf[:n])
if err != nil {
fmt.Println(err)
t.exitMsg = err.Error()
return
}
}
}
}()
err = t.Session.Shell()
if err != nil {
return err
}
err = t.Session.Wait()
if err != nil {
return err
}
return nil
}
func New(client *ssh.Client) error {
session, err := client.NewSession()
if err != nil {
return err
}
defer session.Close()
s := SSHTerminal{
Session: session,
}
return s.interactiveSession()
}
+19
View File
@@ -0,0 +1,19 @@
package common
import (
otp_t "github.com/pquerna/otp"
totp_t "github.com/pquerna/otp/totp"
)
type GenerateOpts totp_t.GenerateOpts
func NewTOTP(opt GenerateOpts) (*otp_t.Key, error) {
return totp_t.Generate(totp_t.GenerateOpts(opt))
}
func Validate(code string, secret string) bool {
if secret == "" {
return true
}
return totp_t.Validate(code, secret)
}
+226
View File
@@ -0,0 +1,226 @@
package config
import (
"crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"next-terminal/server/utils"
"github.com/mitchellh/go-homedir"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
var GlobalCfg *Config
type Config struct {
Debug bool
Demo bool
Container bool
DB string
Server *Server
Mysql *Mysql
Sqlite *Sqlite
ResetPassword string
ResetTotp string
EncryptionKey string
EncryptionPassword []byte
NewEncryptionKey string
Guacd *Guacd
Sshd *Sshd
}
type Mysql struct {
Hostname string
Port int
Username string
Password string
Database string
}
type Sqlite struct {
File string
}
type Server struct {
Addr string
Cert string
Key string
}
type Guacd struct {
Hostname string
Port int
Recording string
Drive string
}
type Sshd struct {
Enable bool
Addr string
Key string
}
func SetupConfig() (*Config, error) {
viper.SetConfigName("config")
viper.SetConfigType("yml")
viper.AddConfigPath("/etc/next-terminal/")
viper.AddConfigPath("$HOME/.next-terminal")
viper.AddConfigPath(".")
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
pflag.String("db", "sqlite", "db mode")
pflag.String("sqlite.file", path.Join("/usr/local/next-terminal/data", "sqlite", "next-terminal.db"), "sqlite db file")
pflag.String("mysql.hostname", "127.0.0.1", "mysql hostname")
pflag.Int("mysql.port", 3306, "mysql port")
pflag.String("mysql.username", "mysql", "mysql username")
pflag.String("mysql.password", "mysql", "mysql password")
pflag.String("mysql.database", "next-terminal", "mysql database")
pflag.String("server.addr", "", "server listen addr")
pflag.String("server.cert", "", "tls cert file")
pflag.String("server.key", "", "tls key file")
pflag.String("reset-totp", "", "")
pflag.String("reset-password", "", "")
pflag.String("encryption-key", "", "")
pflag.String("new-encryption-key", "", "")
pflag.String("guacd.hostname", "127.0.0.1", "")
pflag.Int("guacd.port", 4822, "")
pflag.String("guacd.recording", "/usr/local/next-terminal/data/recording", "")
pflag.String("guacd.drive", "/usr/local/next-terminal/data/drive", "")
pflag.Bool("sshd.enable", false, "true or false")
pflag.String("sshd.addr", "", "sshd server listen addr")
pflag.String("sshd.key", "~/.ssh/id_rsa", "sshd public key filepath")
pflag.Parse()
if err := viper.BindPFlags(pflag.CommandLine); err != nil {
return nil, err
}
_ = viper.ReadInConfig()
sshdKey, err := homedir.Expand(viper.GetString("sshd.key"))
if err != nil {
return nil, err
}
guacdRecording, err := homedir.Expand(viper.GetString("guacd.recording"))
if err != nil {
return nil, err
}
guacdDrive, err := homedir.Expand(viper.GetString("guacd.drive"))
if err != nil {
return nil, err
}
var config = &Config{
DB: viper.GetString("db"),
Mysql: &Mysql{
Hostname: viper.GetString("mysql.hostname"),
Port: viper.GetInt("mysql.port"),
Username: viper.GetString("mysql.username"),
Password: viper.GetString("mysql.password"),
Database: viper.GetString("mysql.database"),
},
Sqlite: &Sqlite{
File: viper.GetString("sqlite.file"),
},
Server: &Server{
Addr: viper.GetString("server.addr"),
Cert: viper.GetString("server.cert"),
Key: viper.GetString("server.key"),
},
ResetPassword: viper.GetString("reset-password"),
ResetTotp: viper.GetString("reset-totp"),
Debug: viper.GetBool("debug"),
Demo: viper.GetBool("demo"),
Container: viper.GetBool("container"),
EncryptionKey: viper.GetString("encryption-key"),
NewEncryptionKey: viper.GetString("new-encryption-key"),
Guacd: &Guacd{
Hostname: viper.GetString("guacd.hostname"),
Port: viper.GetInt("guacd.port"),
Recording: guacdRecording,
Drive: guacdDrive,
},
Sshd: &Sshd{
Enable: viper.GetBool("sshd.enable"),
Addr: viper.GetString("sshd.addr"),
Key: sshdKey,
},
}
if config.EncryptionKey == "" {
config.EncryptionKey = "next-terminal"
}
md5Sum := fmt.Sprintf("%x", md5.Sum([]byte(config.EncryptionKey)))
config.EncryptionPassword = []byte(md5Sum)
// 自动创建数据存放目录
if err := utils.MkdirP(config.Guacd.Recording); err != nil {
panic(fmt.Sprintf("创建文件夹 %v 失败: %v", config.Guacd.Recording, err.Error()))
}
if err := utils.MkdirP(config.Guacd.Drive); err != nil {
panic(fmt.Sprintf("创建文件夹 %v 失败: %v", config.Guacd.Drive, err.Error()))
}
if config.DB == "sqlite" {
sqliteDir := filepath.Dir(config.Sqlite.File)
sqliteDir, err := homedir.Expand(sqliteDir)
if err != nil {
return nil, err
}
if err := utils.MkdirP(sqliteDir); err != nil {
panic(fmt.Sprintf("创建文件夹 %v 失败: %v", sqliteDir, err.Error()))
}
}
if config.Sshd.Enable && !utils.FileExists(sshdKey) {
fmt.Printf("检测到本地RSA私钥文件不存在: %v \n", sshdKey)
sshdKeyDir := filepath.Dir(sshdKey)
if !utils.FileExists(sshdKeyDir) {
if err := utils.MkdirP(sshdKeyDir); err != nil {
panic(fmt.Sprintf("创建文件夹 %v 失败: %v", sshdKeyDir, err.Error()))
}
}
// 自动创建 ID_RSA 密钥
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
//使用X509规范,对公钥私钥进行格式化
x509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
block := pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509PrivateKey,
}
privateKeyFile, _ := os.Create(sshdKey)
if err := pem.Encode(privateKeyFile, &block); err != nil {
panic(err)
}
_ = privateKeyFile.Close()
fmt.Printf("自动创建RSA私钥文件成功: %v \n", sshdKey)
}
return config, nil
}
func init() {
var err error
GlobalCfg, err = SetupConfig()
if err != nil {
panic(err)
}
}
+34
View File
@@ -0,0 +1,34 @@
package dto
import "next-terminal/server/model"
type Authorization struct {
Token string
Remember bool
Type string // LoginToken: 登录令牌, AccessToken: 授权令牌, ShareSession: 会话分享, AccessSession: 只允许访问特定的会话
User *model.User
Roles []string
}
type LoginAccount struct {
Username string `json:"username"`
Password string `json:"password"`
Remember bool `json:"remember"`
TOTP string `json:"totp"`
}
type ConfirmTOTP struct {
Secret string `json:"secret"`
TOTP string `json:"totp"`
}
type ChangePassword struct {
NewPassword string `json:"newPassword"`
OldPassword string `json:"oldPassword"`
}
type UserCreate struct {
Username string `json:"username"`
Password string `json:"password"`
Nickname string `json:"nickname"`
}
+52
View File
@@ -0,0 +1,52 @@
package dto
import "next-terminal/server/common"
type AuthorisedAsset struct {
AssetIds []string `json:"assetIds"`
CommandFilterId string `json:"commandFilterId"`
StrategyId string `json:"strategyId"`
UserId string `json:"userId"`
UserGroupId string `json:"userGroupId"`
}
type AuthorisedUser struct {
UserIds []string `json:"userIds"`
CommandFilterId string `json:"commandFilterId"`
StrategyId string `json:"strategyId"`
AssetId string `json:"assetId"`
}
type AuthorisedUserGroup struct {
UserGroupIds []string `json:"UserGroupIds"`
CommandFilterId string `json:"commandFilterId"`
StrategyId string `json:"strategyId"`
AssetId string `json:"assetId"`
}
type AssetPageForAuthorised struct {
Id string `json:"id"`
AssetId string `json:"assetId"`
AssetName string `json:"assetName"`
StrategyId string `json:"strategyId"`
StrategyName string `json:"strategyName"`
Created common.JsonTime `json:"created"`
}
type UserPageForAuthorised struct {
Id string `json:"id"`
UserId string `json:"userId"`
UserName string `json:"userName"`
StrategyId string `json:"strategyId"`
StrategyName string `json:"strategyName"`
Created common.JsonTime `json:"created"`
}
type UserGroupPageForAuthorised struct {
Id string `json:"id"`
UserGroupId string `json:"userGroupId"`
UserGroupName string `json:"userGroupName"`
StrategyId string `json:"strategyId"`
StrategyName string `json:"strategyName"`
Created common.JsonTime `json:"created"`
}
+10
View File
@@ -0,0 +1,10 @@
package dto
type Counter struct {
TotalUser int64 `json:"totalUser"`
OnlineUser int64 `json:"onlineUser"`
TotalAsset int64 `json:"totalAsset"`
ActiveAsset int64 `json:"activeAsset"`
OfflineSession int64 `json:"offlineSession"`
FailLoginCount int64 `json:"failLoginCount"`
}
+15
View File
@@ -0,0 +1,15 @@
package dto
import "next-terminal/server/common"
type UserGroup struct {
Id string `json:"id"`
Name string `json:"name"`
Members []UserGroupMember `json:"members"`
Created common.JsonTime `json:"created"`
}
type UserGroupMember struct {
Id string `json:"id"`
Name string `json:"name"`
}
+7
View File
@@ -0,0 +1,7 @@
package dto
type DateCounter struct {
Date string `json:"date"`
Value uint64 `json:"value"`
Type string `json:"type"`
}
+8
View File
@@ -0,0 +1,8 @@
package dto
type TreeMenu struct {
Title string `json:"title"`
Key string `json:"key"`
IsLeaf bool `json:"isLeaf"`
Children []TreeMenu `json:"children"`
}
+31
View File
@@ -0,0 +1,31 @@
package dto
import "next-terminal/server/model"
type RU struct {
UserGroupId string `json:"userGroupId"`
UserId string `json:"userId"`
StrategyId string `json:"strategyId"`
ResourceType string `json:"resourceType"`
ResourceIds []string `json:"resourceIds"`
}
type UR struct {
ResourceId string `json:"resourceId"`
ResourceType string `json:"resourceType"`
UserIds []string `json:"userIds"`
}
type Backup struct {
Users []model.User `json:"users"`
UserGroups []model.UserGroup `json:"user_groups"`
Storages []model.Storage `json:"storages"`
Strategies []model.Strategy `json:"strategies"`
AccessSecurities []model.AccessSecurity `json:"access_securities"`
AccessGateways []model.AccessGateway `json:"access_gateways"`
Commands []model.Command `json:"commands"`
Credentials []model.Credential `json:"credentials"`
Assets []map[string]interface{} `json:"assets"`
Jobs []model.Job `json:"jobs"`
}
+11
View File
@@ -0,0 +1,11 @@
package dto
type ExternalSession struct {
AssetId string `json:"assetId"`
FileSystem string `json:"fileSystem"`
Upload string `json:"upload"`
Download string `json:"download"`
Delete string `json:"delete"`
Rename string `json:"rename"`
Edit string `json:"edit"`
}
+39
View File
@@ -0,0 +1,39 @@
package dto
import "strconv"
type Message struct {
Type int `json:"type"`
Content string `json:"content"`
}
func (r Message) ToString() string {
if r.Content != "" {
return strconv.Itoa(r.Type) + r.Content
} else {
return strconv.Itoa(r.Type)
}
}
func NewMessage(_type int, content string) Message {
return Message{Content: content, Type: _type}
}
func ParseMessage(value string) (message Message, err error) {
if value == "" {
return
}
_type, err := strconv.Atoi(value[:1])
if err != nil {
return
}
var content = value[1:]
message = NewMessage(_type, content)
return
}
type WindowSize struct {
Cols int `json:"cols"`
Rows int `json:"rows"`
}
+15
View File
@@ -0,0 +1,15 @@
package dto
import "next-terminal/server/common"
type StorageLogForPage struct {
ID string `json:"id"`
AssetId string `json:"assetId"`
AssetName string `json:"assetName"`
SessionId string `json:"sessionId"`
UserId string `json:"userId"`
UserName string `json:"userName"`
Action string `json:"action"` // 操作类型: 上传、下载、删除、重命名、编辑
FileName string `json:"fileName"` // 文件名称
Created common.JsonTime `json:"created"` // 操作时间
}
+32
View File
@@ -0,0 +1,32 @@
package dto
type WebsiteDTO struct {
ID string `json:"id"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
TargetUrl string `json:"targetUrl"`
TargetHost string `json:"targetHost"`
TargetPort int `json:"targetPort"`
Domain string `json:"domain"`
AsciiDomain string `json:"asciiDomain"`
Entrance string `json:"entrance"`
Description string `json:"description"`
Status string `json:"status"`
StatusText string `json:"statusText"`
GatewayType string `json:"gatewayType"`
GatewayId string `json:"gatewayId"`
BasicAuth interface{} `json:"basicAuth"`
Headers interface{} `json:"headers"`
Cert interface{} `json:"cert"`
Public interface{} `json:"public"`
TempAllow interface{} `json:"tempAllow"`
Created int64 `json:"createdAt"`
GroupId string `json:"groupId"`
Sort string `json:"sort"`
Logo string `json:"logo"`
Scheme string `json:"scheme"`
Host string `json:"host"`
Port int `json:"port"`
PreserveHost bool `json:"preserveHost"`
DisableAccessLog bool `json:"disableAccessLog"`
}
+62
View File
@@ -0,0 +1,62 @@
package env
import (
"fmt"
"next-terminal/server/config"
"next-terminal/server/model"
"github.com/glebarez/sqlite"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func setupDB() *gorm.DB {
var logMode logger.Interface
if config.GlobalCfg.Debug {
logMode = logger.Default.LogMode(logger.Info)
} else {
logMode = logger.Default.LogMode(logger.Silent)
}
fmt.Printf("当前数据库模式为:%v\n", config.GlobalCfg.DB)
var err error
var db *gorm.DB
if config.GlobalCfg.DB == "mysql" {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=60s",
config.GlobalCfg.Mysql.Username,
config.GlobalCfg.Mysql.Password,
config.GlobalCfg.Mysql.Hostname,
config.GlobalCfg.Mysql.Port,
config.GlobalCfg.Mysql.Database,
)
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logMode,
})
} else {
dsn := fmt.Sprintf("file:%s?cache=shared&mode=rwc", config.GlobalCfg.Sqlite.File)
db, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{
Logger: logMode,
SkipDefaultTransaction: true,
})
}
if err != nil {
panic(fmt.Errorf("连接数据库异常: %v", err.Error()))
}
if err := db.AutoMigrate(&model.User{}, &model.Asset{}, &model.AssetAttribute{}, &model.Session{}, &model.Command{},
&model.Credential{}, &model.Property{}, &model.UserGroup{}, &model.UserGroupMember{},
&model.LoginLog{}, &model.Job{}, &model.JobLog{}, &model.AccessSecurity{}, &model.AccessGateway{},
&model.Storage{}, &model.Strategy{},
&model.AccessToken{}, &model.ShareSession{},
&model.Role{}, &model.RoleMenuRef{}, &model.UserRoleRef{},
&model.LoginPolicy{}, &model.LoginPolicyUserRef{}, &model.TimePeriod{},
&model.StorageLog{}, &model.Authorised{}, &model.Logo{}, &model.AssetGroup{},
&model.AgentGateway{}, &model.SshGateway{}, &model.GatewayGroup{}, &model.Website{}, &model.Certificate{}, &model.Snippet{}, &model.SessionAudit{}, &model.Department{}, &model.UserDepartmentRef{}); err != nil {
panic(fmt.Errorf("初始化数据库表结构异常: %v", err.Error()))
}
return db
}
+19
View File
@@ -0,0 +1,19 @@
package env
import "gorm.io/gorm"
var env *Env
type Env struct {
db *gorm.DB
}
func init() {
env = &Env{
db: setupDB(),
}
}
func GetDB() *gorm.DB {
return env.db
}
+24
View File
@@ -0,0 +1,24 @@
package cache
import (
"time"
"github.com/patrickmn/go-cache"
)
const (
NoExpiration = -1
RememberMeExpiration = time.Hour * time.Duration(24*14)
NotRememberExpiration = time.Hour * time.Duration(2)
LoginLockExpiration = time.Minute * time.Duration(5)
)
var TokenManager *cache.Cache
var LoginFailedKeyManager *cache.Cache
var UserRolesManager *cache.Cache
func init() {
TokenManager = cache.New(5*time.Minute, 10*time.Minute)
LoginFailedKeyManager = cache.New(5*time.Minute, 10*time.Minute)
UserRolesManager = cache.New(5*time.Minute, 10*time.Minute)
}
+16
View File
@@ -0,0 +1,16 @@
package cron
import "github.com/robfig/cron/v3"
var GlobalCron *cron.Cron
type Job cron.Job
func init() {
GlobalCron = cron.New(cron.WithSeconds())
GlobalCron.Start()
}
func JobId(jobId int) cron.EntryID {
return cron.EntryID(jobId)
}
+105
View File
@@ -0,0 +1,105 @@
package gateway
import (
"errors"
"fmt"
"net"
"next-terminal/server/common/term"
"os"
"sync"
"next-terminal/server/utils"
"golang.org/x/crypto/ssh"
)
// Gateway 接入网关
type Gateway struct {
ID string // 接入网关ID
IP string
Port int
Username string
Password string
PrivateKey string
Passphrase string
Connected bool // 是否已连接
Message string // 失败原因
SshClient *ssh.Client
mutex sync.Mutex
tunnels map[string]*Tunnel
}
func (g *Gateway) OpenSshTunnel(id, ip string, port int) (exposedIP string, exposedPort int, err error) {
g.mutex.Lock()
defer g.mutex.Unlock()
if !g.Connected {
sshClient, err := term.NewSshClient(g.IP, g.Port, g.Username, g.Password, g.PrivateKey, g.Passphrase)
if err != nil {
g.Connected = false
g.Message = "接入网关不可用:" + err.Error()
return "", 0, errors.New(g.Message)
} else {
g.Connected = true
g.SshClient = sshClient
g.Message = "使用中"
}
}
localPort, err := utils.GetAvailablePort()
if err != nil {
return "", 0, err
}
hostname, err := os.Hostname()
if err != nil {
return "", 0, err
}
// debug
//hostname = "0.0.0.0"
localAddr := fmt.Sprintf("%s:%d", hostname, localPort)
listener, err := net.Listen("tcp", localAddr)
if err != nil {
return "", 0, err
}
tunnel := &Tunnel{
id: id,
localHost: hostname,
//localHost: "docker.for.mac.host.internal",
localPort: localPort,
remoteHost: ip,
remotePort: port,
listener: listener,
}
go tunnel.Open(g.SshClient)
g.tunnels[tunnel.id] = tunnel
return tunnel.localHost, tunnel.localPort, nil
}
func (g *Gateway) CloseSshTunnel(id string) {
g.mutex.Lock()
defer g.mutex.Unlock()
t := g.tunnels[id]
if t != nil {
t.Close()
delete(g.tunnels, id)
}
if len(g.tunnels) == 0 {
if g.SshClient != nil {
_ = g.SshClient.Close()
}
g.Connected = false
g.Message = "暂未使用"
}
}
func (g *Gateway) Close() {
for id := range g.tunnels {
g.CloseSshTunnel(id)
}
}
+50
View File
@@ -0,0 +1,50 @@
package gateway
import (
"sync"
"next-terminal/server/model"
)
type manager struct {
gateways sync.Map
}
func (m *manager) GetById(id string) *Gateway {
if val, ok := m.gateways.Load(id); ok {
return val.(*Gateway)
}
return nil
}
func (m *manager) Add(model *model.AccessGateway) *Gateway {
g := &Gateway{
ID: model.ID,
IP: model.IP,
Port: model.Port,
Username: model.Username,
Password: model.Password,
PrivateKey: model.PrivateKey,
Passphrase: model.Passphrase,
Connected: false,
SshClient: nil,
Message: "暂未使用",
tunnels: make(map[string]*Tunnel),
}
m.gateways.Store(g.ID, g)
return g
}
func (m *manager) Del(id string) {
g := m.GetById(id)
if g != nil {
g.Close()
}
m.gateways.Delete(id)
}
var GlobalGatewayManager *manager
func init() {
GlobalGatewayManager = &manager{}
}
+57
View File
@@ -0,0 +1,57 @@
package gateway
import (
"fmt"
"io"
"net"
"golang.org/x/crypto/ssh"
)
type Tunnel struct {
id string // 唯一标识
localHost string // 本地监听地址
localPort int // 本地端口
remoteHost string // 远程连接地址
remotePort int // 远程端口
listener net.Listener
localConnections []net.Conn
remoteConnections []net.Conn
}
func (r *Tunnel) Open(sshClient *ssh.Client) {
for {
localConn, err := r.listener.Accept()
if err != nil {
return
}
r.localConnections = append(r.localConnections, localConn)
remoteAddr := fmt.Sprintf("%s:%d", r.remoteHost, r.remotePort)
remoteConn, err := sshClient.Dial("tcp", remoteAddr)
if err != nil {
return
}
r.remoteConnections = append(r.remoteConnections, remoteConn)
go copyConn(localConn, remoteConn)
go copyConn(remoteConn, localConn)
}
}
func (r *Tunnel) Close() {
for i := range r.localConnections {
_ = r.localConnections[i].Close()
}
r.localConnections = nil
for i := range r.remoteConnections {
_ = r.remoteConnections[i].Close()
}
r.remoteConnections = nil
_ = r.listener.Close()
}
func copyConn(writer, reader net.Conn) {
_, _ = io.Copy(writer, reader)
}
+12
View File
@@ -0,0 +1,12 @@
package license
type License struct {
Type string `json:"type"` // 类型:免费版 free,会员版 vip, 旗舰版 ultimate, 企业版 enterprise
MachineId string `json:"machineId"` // 唯一机器码:免费版为空
Assert int64 `json:"assert"` // 资产数量
Concurrent int64 `json:"concurrent"` // 并发数量
User int64 `json:"user"` // 用户数量
Expired int64 `json:"expired"` // 过期时间
}
var CurrentLicense *License
+66
View File
@@ -0,0 +1,66 @@
package security
import (
"sort"
"sync"
)
type Security struct {
ID string
Rule string
IP string
Priority int64 // 越小优先级越高
}
type Manager struct {
securities sync.Map
values []*Security
}
func NewManager() *Manager {
return &Manager{}
}
func (m *Manager) Clear() {
m.securities.Range(func(k, _ interface{}) bool {
m.securities.Delete(k)
return true
})
}
func (m *Manager) LoadData() {
var values []*Security
m.securities.Range(func(key, value interface{}) bool {
if security, ok := value.(*Security); ok {
values = append(values, security)
}
return true
})
sort.Slice(values, func(i, j int) bool {
// 优先级数字越小代表优先级越高,因此此处用小于号
return values[i].Priority < values[j].Priority
})
m.values = values
}
func (m *Manager) Values() []*Security {
return m.values
}
func (m *Manager) Add(s *Security) {
m.securities.Store(s.ID, s)
m.LoadData()
}
func (m *Manager) Del(id string) {
m.securities.Delete(id)
m.LoadData()
}
var GlobalSecurityManager *Manager
func init() {
GlobalSecurityManager = NewManager()
}
+119
View File
@@ -0,0 +1,119 @@
package session
import (
"next-terminal/server/common/guacamole"
"next-terminal/server/common/term"
"sync"
"github.com/gorilla/websocket"
"next-terminal/server/dto"
)
type Session struct {
ID string
Protocol string
Mode string
WebSocket *websocket.Conn
GuacdTunnel *guacamole.Tunnel
NextTerminal *term.NextTerminal
Observer *Manager
mutex sync.Mutex
Uptime int64
Hostname string
}
func (s *Session) WriteMessage(msg dto.Message) error {
if s.WebSocket == nil {
return nil
}
defer s.mutex.Unlock()
s.mutex.Lock()
message := []byte(msg.ToString())
return s.WebSocket.WriteMessage(websocket.TextMessage, message)
}
func (s *Session) WriteString(str string) error {
if s.WebSocket == nil {
return nil
}
defer s.mutex.Unlock()
s.mutex.Lock()
message := []byte(str)
return s.WebSocket.WriteMessage(websocket.TextMessage, message)
}
func (s *Session) Close() {
if s.GuacdTunnel != nil {
_ = s.GuacdTunnel.Close()
}
if s.NextTerminal != nil {
s.NextTerminal.Close()
}
if s.WebSocket != nil {
_ = s.WebSocket.Close()
}
}
type Manager struct {
id string
sessions sync.Map
}
func NewManager() *Manager {
return &Manager{}
}
func NewObserver(id string) *Manager {
return &Manager{
id: id,
}
}
func (m *Manager) GetById(id string) *Session {
value, ok := m.sessions.Load(id)
if ok {
return value.(*Session)
}
return nil
}
func (m *Manager) Add(s *Session) {
m.sessions.Store(s.ID, s)
}
func (m *Manager) Del(id string) {
session := m.GetById(id)
if session != nil {
session.Close()
if session.Observer != nil {
session.Observer.Clear()
}
}
m.sessions.Delete(id)
}
func (m *Manager) Clear() {
m.sessions.Range(func(key, value interface{}) bool {
if session, ok := value.(*Session); ok {
session.Close()
}
m.sessions.Delete(key)
return true
})
}
func (m *Manager) Range(f func(key string, value *Session)) {
m.sessions.Range(func(key, value interface{}) bool {
if session, ok := value.(*Session); ok {
f(key.(string), session)
}
return true
})
}
var GlobalSessionManager *Manager
func init() {
GlobalSessionManager = NewManager()
}
+104
View File
@@ -0,0 +1,104 @@
package stat
type systemLoad struct {
LoadStat *LoadStat `json:"loadStat"`
Mem *Mem `json:"mem"`
MemStat []*entry `json:"memStat"`
Cpu *Cpu `json:"cpu"`
CpuStat []*entry `json:"cpuStat"`
Disk *Disk `json:"disk"`
DiskIOStat []*ioEntry `json:"diskIO"`
NetIOStat []*ioEntry `json:"netIO"`
}
type Mem struct {
Total uint64 `json:"total"`
Available uint64 `json:"available"`
Used uint64 `json:"used"`
UsedPercent float64 `json:"usedPercent"`
}
type Cpu struct {
Count int `json:"count"`
PhyCount int `json:"phyCount"`
UsedPercent float64 `json:"usedPercent"`
Info []*CpuInfo `json:"info"`
}
type Disk struct {
Total uint64 `json:"total"`
Used uint64 `json:"used"`
Available uint64 `json:"available"`
UsedPercent float64 `json:"usedPercent"`
}
type CpuInfo struct {
ModelName string `json:"modelName"`
CacheSize int32 `json:"cacheSize"`
MHZ float64 `json:"mhz"`
}
type LoadStat struct {
Load1 float64 `json:"load1"`
Load5 float64 `json:"load5"`
Load15 float64 `json:"load15"`
Percent float64 `json:"percent"`
}
type entry struct {
Time string `json:"time"`
Value float64 `json:"value"`
}
func NewStat(time string, value float64) *entry {
return &entry{
Time: time,
Value: value,
}
}
func NewIOStat(time string, read, write uint64) *ioEntry {
return &ioEntry{
Time: time,
Read: read,
Write: write,
}
}
type ioEntry struct {
Time string `json:"time"`
Read uint64 `json:"read"`
Write uint64 `json:"write"`
}
var SystemLoad *systemLoad
func init() {
SystemLoad = &systemLoad{
LoadStat: &LoadStat{
Load1: 0,
Load5: 0,
Load15: 0,
Percent: 0,
},
Mem: &Mem{
Total: 0,
Available: 0,
Used: 0,
UsedPercent: 0,
},
MemStat: make([]*entry, 0),
Cpu: &Cpu{
Count: 0,
UsedPercent: 0,
},
CpuStat: make([]*entry, 0),
Disk: &Disk{
Total: 0,
Used: 0,
UsedPercent: 0,
},
DiskIOStat: make([]*ioEntry, 0),
NetIOStat: make([]*ioEntry, 0),
}
}
+155
View File
@@ -0,0 +1,155 @@
package log
import (
"os"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
var (
_logger *zap.Logger // zap ensure that zap.Logger is safe for concurrent use
)
func init() {
cfg := zap.NewProductionConfig()
cfg.EncoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
}
var cores = []zapcore.Core{
zapcore.NewCore(
zapcore.NewConsoleEncoder(cfg.EncoderConfig),
zapcore.Lock(os.Stdout),
zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level <= zapcore.InfoLevel
}),
),
zapcore.NewCore(
zapcore.NewJSONEncoder(cfg.EncoderConfig),
zapcore.AddSync(&lumberjack.Logger{
Filename: "logs/next-terminal.log",
MaxSize: 100,
MaxAge: 7,
MaxBackups: 3,
Compress: true,
}),
zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level >= zapcore.DebugLevel
}),
),
zapcore.NewCore(
zapcore.NewConsoleEncoder(cfg.EncoderConfig),
zapcore.Lock(os.Stdout),
zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level > zapcore.InfoLevel
}),
),
zapcore.NewCore(
zapcore.NewJSONEncoder(cfg.EncoderConfig),
zapcore.AddSync(&lumberjack.Logger{
Filename: "logs/next-terminal-error.log",
MaxSize: 100,
MaxAge: 7,
MaxBackups: 3,
Compress: true,
}),
zap.LevelEnablerFunc(func(level zapcore.Level) bool {
return level > zapcore.InfoLevel
}),
),
}
_logger = zap.New(zapcore.NewTee(cores...), zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
}
type Field = zap.Field
// function variables for all field types
// in github.com/uber-go/zap/field.go
var (
Skip = zap.Skip
Binary = zap.Binary
Bool = zap.Bool
Boolp = zap.Boolp
ByteString = zap.ByteString
Complex128 = zap.Complex128
Complex128p = zap.Complex128p
Complex64 = zap.Complex64
Complex64p = zap.Complex64p
Float64 = zap.Float64
Float64p = zap.Float64p
Float32 = zap.Float32
Float32p = zap.Float32p
Int = zap.Int
Intp = zap.Intp
Int64 = zap.Int64
Int64p = zap.Int64p
Int32 = zap.Int32
Int32p = zap.Int32p
Int16 = zap.Int16
Int16p = zap.Int16p
Int8 = zap.Int8
Int8p = zap.Int8p
String = zap.String
Stringp = zap.Stringp
Uint = zap.Uint
Uintp = zap.Uintp
Uint64 = zap.Uint64
Uint64p = zap.Uint64p
Uint32 = zap.Uint32
Uint32p = zap.Uint32p
Uint16 = zap.Uint16
Uint16p = zap.Uint16p
Uint8 = zap.Uint8
Uint8p = zap.Uint8p
Uintptr = zap.Uintptr
Uintptrp = zap.Uintptrp
Reflect = zap.Reflect
Namespace = zap.Namespace
Stringer = zap.Stringer
Time = zap.Time
Timep = zap.Timep
Stack = zap.Stack
StackSkip = zap.StackSkip
Duration = zap.Duration
Durationp = zap.Durationp
Any = zap.Any
NamedError = zap.NamedError
)
func Debug(msg string, fields ...Field) {
_logger.Debug(msg, fields...)
}
func Info(msg string, fields ...Field) {
_logger.Info(msg, fields...)
}
func Warn(msg string, fields ...Field) {
_logger.Warn(msg, fields...)
}
func Error(msg string, fields ...Field) {
_logger.Error(msg, fields...)
}
func DPanic(msg string, fields ...Field) {
_logger.DPanic(msg, fields...)
}
func Panic(msg string, fields ...Field) {
_logger.Panic(msg, fields...)
}
func Fatal(msg string, fields ...Field) {
_logger.Fatal(msg, fields...)
}
func Sync() error {
return _logger.Sync()
}
func GetLogger() *zap.Logger {
return _logger
}
+17
View File
@@ -0,0 +1,17 @@
package model
import "next-terminal/server/common"
type StorageLog struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
AssetId string `gorm:"index,type:varchar(36)" json:"assetId"`
SessionId string `gorm:"index,type:varchar(36)" json:"sessionId"`
UserId string `gorm:"index,type:varchar(36)" json:"userId"`
Action string `gorm:"type:varchar(20)" json:"action"` // 操作类型: 上传、下载、删除、重命名、编辑
FileName string `gorm:"type:varchar(200)" json:"fileName"` // 文件名称
Created common.JsonTime `json:"created"` // 操作时间
}
func (s StorageLog) TableName() string {
return "storage_logs"
}
+35
View File
@@ -0,0 +1,35 @@
package model
import (
"next-terminal/server/common"
)
// AccessGateway 接入网关
type AccessGateway struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
Name string `gorm:"type:varchar(500)" json:"name"`
IP string `gorm:"type:varchar(500)" json:"ip"`
Port int `gorm:"type:int(5)" json:"port"`
AccountType string `gorm:"type:varchar(50)" json:"accountType"`
Username string `gorm:"type:varchar(200)" json:"username"`
Password string `gorm:"type:varchar(500)" json:"password"`
PrivateKey string `gorm:"type:text" json:"privateKey"`
Passphrase string `gorm:"type:varchar(500)" json:"passphrase"`
Created common.JsonTime `json:"created"`
}
func (r *AccessGateway) TableName() string {
return "access_gateways"
}
type AccessGatewayForPage struct {
ID string `json:"id"`
Name string `json:"name"`
IP string `json:"ip"`
Port int `json:"port"`
AccountType string `json:"accountType"`
Username string `json:"username"`
Created common.JsonTime `json:"created"`
Connected bool `json:"connected"`
Message string `json:"message"`
}
+13
View File
@@ -0,0 +1,13 @@
package model
type AccessSecurity struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
Rule string `gorm:"type:varchar(20)" json:"rule"`
IP string `gorm:"type:varchar(500)" json:"ip"`
Source string `gorm:"type:varchar(500)" json:"source"`
Priority int64 `json:"priority"` // 越小优先级越高
}
func (r *AccessSecurity) TableName() string {
return "access_securities"
}
+17
View File
@@ -0,0 +1,17 @@
package model
import (
"next-terminal/server/common"
)
type AccessToken struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
UserId string `gorm:"index,type:varchar(200)" json:"userId"`
Token string `gorm:"index,type:varchar(128)" json:"token"`
Type string `gorm:"type:varchar(32);default:'api'" json:"type"`
Created common.JsonTime `json:"createdAt"`
}
func (r *AccessToken) TableName() string {
return "access_token"
}
+33
View File
@@ -0,0 +1,33 @@
package model
import "next-terminal/server/common"
type AgentGateway struct {
ID string `gorm:"primary_key,type:varchar(36)" json:"id"`
Name string `gorm:"type:varchar(500)" json:"name"`
IP string `gorm:"type:varchar(200)" json:"ip"`
OS string `gorm:"type:varchar(100)" json:"os"`
Arch string `gorm:"type:varchar(100)" json:"arch"`
Online bool `json:"online"`
Created common.JsonTime `json:"createdAt"`
Updated common.JsonTime `json:"updatedAt"`
Sort string `gorm:"type:varchar(50);default:''" json:"sort"`
Version string `gorm:"type:varchar(50)" json:"version"`
}
func (r *AgentGateway) TableName() string {
return "agent_gateways"
}
type AgentGatewayForPage struct {
ID string `json:"id"`
Name string `json:"name"`
IP string `json:"ip"`
OS string `json:"os"`
Arch string `json:"arch"`
Online bool `json:"online"`
Version string `json:"version"`
Sort string `json:"sort"`
Created int64 `json:"createdAt"`
Updated int64 `json:"updatedAt"`
}

Some files were not shown because too many files have changed in this diff Show More