提交 v1.3.0 beta

This commit is contained in:
dushixiang
2022-10-23 20:05:13 +08:00
parent 4ff4d37442
commit 112435199a
329 changed files with 18340 additions and 58458 deletions

70
server/api/abi/abi.go Normal file
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
}

View File

@ -2,6 +2,8 @@ package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -23,7 +25,7 @@ func (api AccessGatewayApi) AccessGatewayCreateEndpoint(c echo.Context) error {
}
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := repository.GatewayRepository.Create(context.TODO(), &item); err != nil {
return err
@ -38,11 +40,14 @@ func (api AccessGatewayApi) AccessGatewayAllEndpoint(c echo.Context) error {
if err != nil {
return err
}
var simpleGateways = make([]model.AccessGatewayForPage, 0)
for i := 0; i < len(gateways); i++ {
simpleGateways = append(simpleGateways, model.AccessGatewayForPage{ID: gateways[i].ID, Name: gateways[i].Name})
items := make([]maps.Map, len(gateways))
for i, e := range gateways {
items[i] = maps.Map{
"id": e.ID,
"name": e.Name,
}
}
return Success(c, simpleGateways)
return Success(c, items)
}
func (api AccessGatewayApi) AccessGatewayPagingEndpoint(c echo.Context) error {
@ -66,7 +71,7 @@ func (api AccessGatewayApi) AccessGatewayPagingEndpoint(c echo.Context) error {
}
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})

View File

@ -3,17 +3,17 @@ package api
import (
"context"
"errors"
"next-terminal/server/common"
"next-terminal/server/common/nt"
"path"
"strconv"
"strings"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/dto"
"next-terminal/server/global/cache"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/totp"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
@ -50,7 +50,7 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
}
if user.Status == constant.StatusDisabled {
if user.Status == nt.StatusDisabled {
return Fail(c, -1, "该账户已停用")
}
@ -64,11 +64,24 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
}
// 账号密码正确,需要进行两步验证
if user.TOTPSecret != "" && user.TOTPSecret != "-" {
return Fail(c, 0, "")
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)
token, err := api.LoginSuccess(loginAccount, user, c.RealIP())
if err != nil {
return err
}
@ -80,12 +93,17 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
return Success(c, token)
}
func (api AccountApi) LoginSuccess(loginAccount dto.LoginAccount, user model.User) (string, error) {
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: constant.LoginToken,
Type: nt.LoginToken,
Remember: loginAccount.Remember,
User: &user,
}
@ -97,75 +115,12 @@ func (api AccountApi) LoginSuccess(loginAccount dto.LoginAccount, user model.Use
cache.TokenManager.Set(token, authorization, cache.NotRememberExpiration)
}
b := true
// 修改登录状态
err := repository.UserRepository.Update(context.TODO(), &model.User{Online: true, ID: user.ID})
err := repository.UserRepository.Update(context.TODO(), &model.User{Online: &b, ID: user.ID})
return token, err
}
func (api AccountApi) LoginWithTotpEndpoint(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分钟后再试")
}
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 == constant.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 !totp.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)
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
}
return Success(c, token)
}
func (api AccountApi) LogoutEndpoint(c echo.Context) error {
token := GetToken(c)
service.UserService.Logout(token)
@ -183,7 +138,7 @@ func (api AccountApi) ConfirmTOTPEndpoint(c echo.Context) error {
return err
}
if !totp.Validate(confirmTOTP.TOTP, confirmTOTP.Secret) {
if !common.Validate(confirmTOTP.TOTP, confirmTOTP.Secret) {
return Fail(c, -1, "TOTP 验证失败,请重试")
}
@ -202,7 +157,7 @@ func (api AccountApi) ConfirmTOTPEndpoint(c echo.Context) error {
func (api AccountApi) ReloadTOTPEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
key, err := totp.NewTOTP(totp.GenerateOpts{
key, err := common.NewTOTP(common.GenerateOpts{
Issuer: c.Request().Host,
AccountName: account.Username,
})
@ -270,59 +225,68 @@ func (api AccountApi) ChangePasswordEndpoint(c echo.Context) error {
}
type AccountInfo struct {
Id string `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Type string `json:"type"`
EnableTotp bool `json:"enableTotp"`
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 []string `json:"menus"`
}
func (api AccountApi) InfoEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
if strings.EqualFold("anonymous", account.Type) {
return Success(c, account)
}
user, err := repository.UserRepository.FindById(context.TODO(), account.ID)
user, err := service.UserService.FindById(account.ID)
if err != nil {
return err
}
var menus []string
if service.UserService.IsSuperAdmin(account.ID) {
menus = 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)
menus = append(menus, items...)
}
}
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) AccountAssetEndpoint(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")
owner := c.QueryParam("owner")
sharer := c.QueryParam("sharer")
userGroupId := c.QueryParam("userGroupId")
ip := c.QueryParam("ip")
order := c.QueryParam("order")
field := c.QueryParam("field")
func (api AccountApi) MenuEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
items, total, err := repository.AssetRepository.Find(context.TODO(), pageIndex, pageSize, name, protocol, tags, account, owner, sharer, userGroupId, ip, order, field)
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
}
for i := range items {
items[i].IP = ""
items[i].Port = 0
var menus []string
for _, role := range roles {
items := service.RoleService.GetMenuListByRole(role)
menus = append(menus, items...)
}
return Success(c, Map{
"total": total,
"items": items,
})
return Success(c, menus)
}
func (api AccountApi) AccountStorageEndpoint(c echo.Context) error {
@ -364,3 +328,11 @@ func (api AccountApi) AccessTokenGenEndpoint(c echo.Context) error {
}
return Success(c, nil)
}
func (api AccountApi) AccessTokenDelEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
if err := service.AccessTokenService.DelAccessToken(context.Background(), account.ID); err != nil {
return err
}
return Success(c, nil)
}

View File

@ -1,25 +1,23 @@
package api
import (
"next-terminal/server/constant"
"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"
"github.com/labstack/echo/v4"
)
type Map map[string]interface{}
func Fail(c echo.Context, code int, message string) error {
return c.JSON(200, Map{
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, Map{
return c.JSON(200, maps.Map{
"code": code,
"message": message,
"data": data,
@ -27,7 +25,7 @@ func FailWithData(c echo.Context, code int, message string, data interface{}) er
}
func Success(c echo.Context, data interface{}) error {
return c.JSON(200, Map{
return c.JSON(200, maps.Map{
"code": 1,
"message": "success",
"data": data,
@ -35,11 +33,11 @@ func Success(c echo.Context, data interface{}) error {
}
func GetToken(c echo.Context) string {
token := c.Request().Header.Get(constant.Token)
token := c.Request().Header.Get(nt.Token)
if len(token) > 0 {
return token
}
return c.QueryParam(constant.Token)
return c.QueryParam(nt.Token)
}
func GetCurrentAccount(c echo.Context) (*model.User, bool) {
@ -50,20 +48,3 @@ func GetCurrentAccount(c echo.Context) (*model.User, bool) {
}
return nil, false
}
func HasPermission(c echo.Context, owner string) bool {
// 检测是否登录
account, found := GetCurrentAccount(c)
if !found {
return false
}
// 检测是否为管理人员
if constant.TypeAdmin == account.Type {
return true
}
// 检测是否为所有者
if owner == account.ID {
return true
}
return false
}

View File

@ -5,10 +5,12 @@ import (
"context"
"encoding/csv"
"errors"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"strconv"
"strings"
"next-terminal/server/constant"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
@ -75,13 +77,13 @@ func (assetApi AssetApi) AssetImportEndpoint(c echo.Context) error {
Protocol: record[1],
IP: record[2],
Port: port,
AccountType: constant.Custom,
AccountType: nt.Custom,
Username: record[4],
Password: record[5],
PrivateKey: record[6],
Passphrase: record[7],
Description: record[8],
Created: utils.NowJsonTime(),
Created: common.NowJsonTime(),
Owner: account.ID,
Active: true,
}
@ -114,22 +116,18 @@ func (assetApi AssetApi) AssetPagingEndpoint(c echo.Context) error {
name := c.QueryParam("name")
protocol := c.QueryParam("protocol")
tags := c.QueryParam("tags")
owner := c.QueryParam("owner")
sharer := c.QueryParam("sharer")
userGroupId := c.QueryParam("userGroupId")
ip := c.QueryParam("ip")
active := c.QueryParam("active")
order := c.QueryParam("order")
field := c.QueryParam("field")
account, _ := GetCurrentAccount(c)
items, total, err := repository.AssetRepository.Find(context.TODO(), pageIndex, pageSize, name, protocol, tags, account, owner, sharer, userGroupId, ip, order, field)
items, total, err := repository.AssetRepository.Find(context.Background(), pageIndex, pageSize, name, protocol, tags, ip, active, order, field)
if err != nil {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -137,16 +135,22 @@ func (assetApi AssetApi) AssetPagingEndpoint(c echo.Context) error {
func (assetApi AssetApi) AssetAllEndpoint(c echo.Context) error {
protocol := c.QueryParam("protocol")
items, _ := repository.AssetRepository.FindByProtocol(context.TODO(), 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")
if err := assetApi.PreCheckAssetPermission(c, id); err != nil {
return err
}
m := echo.Map{}
if err := c.Bind(&m); err != nil {
return err
@ -161,9 +165,6 @@ func (assetApi AssetApi) AssetDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
split := strings.Split(id, ",")
for i := range split {
if err := assetApi.PreCheckAssetPermission(c, split[i]); err != nil {
return err
}
if err := service.AssetService.DeleteById(split[i]); err != nil {
return err
}
@ -174,9 +175,6 @@ func (assetApi AssetApi) AssetDeleteEndpoint(c echo.Context) error {
func (assetApi AssetApi) AssetGetEndpoint(c echo.Context) (err error) {
id := c.Param("id")
if err := assetApi.PreCheckAssetPermission(c, id); err != nil {
return err
}
var item model.Asset
if item, err = service.AssetService.FindByIdAndDecrypt(context.TODO(), id); err != nil {
@ -202,20 +200,17 @@ func (assetApi AssetApi) AssetTcpingEndpoint(c echo.Context) (err error) {
return err
}
active, err := service.AssetService.CheckStatus(item.AccessGatewayId, item.IP, item.Port)
if item.Active != active {
if err := repository.AssetRepository.UpdateActiveById(context.TODO(), active, item.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, Map{
return Success(c, maps.Map{
"active": active,
"message": message,
})
@ -232,25 +227,9 @@ func (assetApi AssetApi) AssetTagsEndpoint(c echo.Context) (err error) {
func (assetApi AssetApi) AssetChangeOwnerEndpoint(c echo.Context) (err error) {
id := c.Param("id")
if err := assetApi.PreCheckAssetPermission(c, id); err != nil {
return err
}
owner := c.QueryParam("owner")
if err := repository.AssetRepository.UpdateById(context.TODO(), &model.Asset{Owner: owner}, id); err != nil {
return err
}
return Success(c, "")
}
func (assetApi AssetApi) PreCheckAssetPermission(c echo.Context, id string) error {
item, err := repository.AssetRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
if !HasPermission(c, item.Owner) {
return errors.New("permission denied")
}
return nil
}

136
server/api/authorised.go Normal file
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)
}

15
server/api/branding.go Normal file
View File

@ -0,0 +1,15 @@
package api
import (
"next-terminal/server/branding"
"next-terminal/server/common/maps"
"github.com/labstack/echo/v4"
)
func Branding(c echo.Context) error {
return Success(c, maps.Map{
"name": branding.Name,
"copyright": branding.Copyright,
})
}

View File

@ -2,7 +2,8 @@ package api
import (
"context"
"errors"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -24,7 +25,7 @@ func (api CommandApi) CommandCreateEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
item.Owner = account.ID
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := repository.CommandRepository.Create(context.TODO(), &item); err != nil {
return err
@ -34,8 +35,7 @@ func (api CommandApi) CommandCreateEndpoint(c echo.Context) error {
}
func (api CommandApi) CommandAllEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
items, err := repository.CommandRepository.FindByUser(context.TODO(), account)
items, err := repository.CommandRepository.FindAll(context.Background())
if err != nil {
return err
}
@ -47,17 +47,16 @@ func (api CommandApi) CommandPagingEndpoint(c echo.Context) error {
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
name := c.QueryParam("name")
content := c.QueryParam("content")
account, _ := GetCurrentAccount(c)
order := c.QueryParam("order")
field := c.QueryParam("field")
items, total, err := repository.CommandRepository.Find(context.TODO(), pageIndex, pageSize, name, content, order, field, account)
items, total, err := repository.CommandRepository.Find(context.TODO(), pageIndex, pageSize, name, content, order, field)
if err != nil {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -65,9 +64,6 @@ func (api CommandApi) CommandPagingEndpoint(c echo.Context) error {
func (api CommandApi) CommandUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
if err := api.PreCheckCommandPermission(c, id); err != nil {
return err
}
var item model.Command
if err := c.Bind(&item); err != nil {
@ -85,9 +81,6 @@ func (api CommandApi) CommandDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
split := strings.Split(id, ",")
for i := range split {
if err := api.PreCheckCommandPermission(c, split[i]); err != nil {
return err
}
if err := repository.CommandRepository.DeleteById(context.TODO(), split[i]); err != nil {
return err
}
@ -97,11 +90,6 @@ func (api CommandApi) CommandDeleteEndpoint(c echo.Context) error {
func (api CommandApi) CommandGetEndpoint(c echo.Context) (err error) {
id := c.Param("id")
if err := api.PreCheckCommandPermission(c, id); err != nil {
return err
}
var item model.Command
if item, err = repository.CommandRepository.FindById(context.TODO(), id); err != nil {
return err
@ -111,26 +99,9 @@ func (api CommandApi) CommandGetEndpoint(c echo.Context) (err error) {
func (api CommandApi) CommandChangeOwnerEndpoint(c echo.Context) (err error) {
id := c.Param("id")
if err := api.PreCheckCommandPermission(c, id); err != nil {
return err
}
owner := c.QueryParam("owner")
if err := repository.CommandRepository.UpdateById(context.TODO(), &model.Command{Owner: owner}, id); err != nil {
return err
}
return Success(c, "")
}
func (api CommandApi) PreCheckCommandPermission(c echo.Context, id string) error {
item, err := repository.CommandRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
if !HasPermission(c, item.Owner) {
return errors.New("permission denied")
}
return nil
}

View File

@ -3,12 +3,13 @@ package api
import (
"context"
"encoding/base64"
"errors"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"strconv"
"strings"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
@ -20,7 +21,7 @@ import (
type CredentialApi struct{}
func (api CredentialApi) CredentialAllEndpoint(c echo.Context) error {
items, err := repository.CredentialRepository.FindByUser(context.TODO())
items, err := repository.CredentialRepository.FindByAll(context.TODO())
if err != nil {
return err
}
@ -35,10 +36,10 @@ func (api CredentialApi) CredentialCreateEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
item.Owner = account.ID
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
switch item.Type {
case constant.Custom:
case nt.Custom:
item.PrivateKey = "-"
item.Passphrase = "-"
if item.Username == "" {
@ -47,7 +48,7 @@ func (api CredentialApi) CredentialCreateEndpoint(c echo.Context) error {
if item.Password == "" {
item.Password = "-"
}
case constant.PrivateKey:
case nt.PrivateKey:
item.Password = "-"
if item.Username == "" {
item.Username = "-"
@ -79,13 +80,12 @@ func (api CredentialApi) CredentialPagingEndpoint(c echo.Context) error {
order := c.QueryParam("order")
field := c.QueryParam("field")
account, _ := GetCurrentAccount(c)
items, total, err := repository.CredentialRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field, account)
items, total, err := repository.CredentialRepository.Find(context.TODO(), pageIndex, pageSize, name, order, field)
if err != nil {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -94,17 +94,13 @@ func (api CredentialApi) CredentialPagingEndpoint(c echo.Context) error {
func (api CredentialApi) CredentialUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
if err := api.PreCheckCredentialPermission(c, id); err != nil {
return err
}
var item model.Credential
if err := c.Bind(&item); err != nil {
return err
}
switch item.Type {
case constant.Custom:
case nt.Custom:
item.PrivateKey = "-"
item.Passphrase = "-"
if item.Username == "" {
@ -120,7 +116,7 @@ func (api CredentialApi) CredentialUpdateEndpoint(c echo.Context) error {
}
item.Password = base64.StdEncoding.EncodeToString(encryptedCBC)
}
case constant.PrivateKey:
case nt.PrivateKey:
item.Password = "-"
if item.Username == "" {
item.Username = "-"
@ -161,9 +157,6 @@ func (api CredentialApi) CredentialDeleteEndpoint(c echo.Context) error {
id := c.Param("id")
split := strings.Split(id, ",")
for i := range split {
if err := api.PreCheckCredentialPermission(c, split[i]); err != nil {
return err
}
if err := repository.CredentialRepository.DeleteById(context.TODO(), split[i]); err != nil {
return err
}
@ -174,44 +167,21 @@ func (api CredentialApi) CredentialDeleteEndpoint(c echo.Context) error {
func (api CredentialApi) CredentialGetEndpoint(c echo.Context) error {
id := c.Param("id")
if err := api.PreCheckCredentialPermission(c, id); err != nil {
return err
}
item, err := service.CredentialService.FindByIdAndDecrypt(context.TODO(), id)
if err != nil {
return err
}
if !HasPermission(c, item.Owner) {
return errors.New("permission denied")
}
return Success(c, item)
}
func (api CredentialApi) CredentialChangeOwnerEndpoint(c echo.Context) error {
id := c.Param("id")
if err := api.PreCheckCredentialPermission(c, id); err != nil {
return err
}
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) PreCheckCredentialPermission(c echo.Context, id string) error {
item, err := repository.CredentialRepository.FindById(context.TODO(), id)
if err != nil {
return err
}
if !HasPermission(c, item.Owner) {
return errors.New("permission denied")
}
return nil
}

View File

@ -4,13 +4,13 @@ import (
"context"
"fmt"
"net/http"
"next-terminal/server/common/guacamole"
"next-terminal/server/common/nt"
"path"
"strconv"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/global/session"
"next-terminal/server/guacd"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
@ -34,6 +34,8 @@ const (
)
var UpGrader = websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
CheckOrigin: func(r *http.Request) bool {
return true
},
@ -46,7 +48,7 @@ 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.Errorf("升级为WebSocket协议失败%v", err.Error())
log.Warn("升级为WebSocket协议失败", log.NamedError("err", err))
return err
}
ctx := context.TODO()
@ -58,7 +60,7 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
intWidth, _ := strconv.Atoi(width)
intHeight, _ := strconv.Atoi(height)
configuration := guacd.NewConfiguration()
configuration := guacamole.NewConfiguration()
propertyMap := repository.PropertyRepository.FindAllMap(ctx)
@ -74,14 +76,14 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
if s.AccessGatewayId != "" && s.AccessGatewayId != "-" {
g, err := service.GatewayService.GetGatewayById(s.AccessGatewayId)
if err != nil {
utils.Disconnect(ws, AccessGatewayUnAvailable, "获取接入网关失败:"+err.Error())
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 {
utils.Disconnect(ws, AccessGatewayCreateError, "创建SSH隧道失败"+err.Error())
guacamole.Disconnect(ws, AccessGatewayCreateError, "创建SSH隧道失败"+err.Error())
return nil
}
s.IP = exposedIP
@ -108,12 +110,12 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
addr := config.GlobalCfg.Guacd.Hostname + ":" + strconv.Itoa(config.GlobalCfg.Guacd.Port)
asset := fmt.Sprintf("%s:%s", configuration.GetParameter("hostname"), configuration.GetParameter("port"))
log.Debugf("[%v] 新建 guacd 会话, guacd=%v, asset=%v", sessionId, addr, asset)
log.Debug("新建 guacd 会话", log.String("sessionId", sessionId), log.String("addr", addr), log.String("asset", asset))
guacdTunnel, err := guacd.NewTunnel(addr, configuration)
guacdTunnel, err := guacamole.NewTunnel(addr, configuration)
if err != nil {
utils.Disconnect(ws, NewTunnelError, err.Error())
log.Printf("[%v] 建立连接失败: %v", sessionId, err.Error())
guacamole.Disconnect(ws, NewTunnelError, err.Error())
log.Error("建立连接失败", log.String("sessionId", sessionId), log.NamedError("err", err))
return err
}
@ -125,11 +127,11 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
GuacdTunnel: guacdTunnel,
}
if configuration.Protocol == constant.SSH {
if configuration.Protocol == nt.SSH {
nextTerminal, err := CreateNextTerminalBySession(s)
if err != nil {
utils.Disconnect(ws, NewSshClientError, "建立SSH客户端失败: "+err.Error())
log.Printf("[%v] 建立 ssh 客户端失败: %v", sessionId, err.Error())
guacamole.Disconnect(ws, NewSshClientError, "建立SSH客户端失败: "+err.Error())
log.Debug("建立 ssh 客户端失败", log.String("sessionId", sessionId), log.NamedError("err", err))
return err
}
nextSession.NextTerminal = nextTerminal
@ -141,15 +143,15 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
ConnectionId: guacdTunnel.UUID,
Width: intWidth,
Height: intHeight,
Status: constant.Connecting,
Recording: configuration.GetParameter(guacd.RecordingPath),
Status: nt.Connecting,
Recording: configuration.GetParameter(guacamole.RecordingPath),
}
if sess.Recording == "" {
// 未录屏时无需审计
sess.Reviewed = true
}
// 创建新会话
log.Debugf("[%v] 新建会话成功: %v", sessionId, sess.ConnectionId)
log.Debug("新建会话成功", log.String("sessionId", sessionId))
if err := repository.SessionRepository.UpdateById(ctx, &sess, sessionId); err != nil {
return err
}
@ -161,7 +163,7 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
for {
_, message, err := ws.ReadMessage()
if err != nil {
log.Debugf("[%v] WebSocket已关闭, %v", sessionId, err.Error())
log.Debug("WebSocket已关闭", log.String("sessionId", sessionId), log.NamedError("err", err))
// guacdTunnel.Read() 会阻塞所以要先把guacdTunnel客户端关闭才能退出Guacd循环
_ = guacdTunnel.Close()
@ -176,23 +178,22 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error {
}
}
func (api GuacamoleApi) setAssetConfig(attributes map[string]string, s model.Session, configuration *guacd.Configuration) {
func (api GuacamoleApi) setAssetConfig(attributes map[string]string, s model.Session, configuration *guacamole.Configuration) {
for key, value := range attributes {
if guacd.DrivePath == key {
if guacamole.DrivePath == key {
// 忽略该参数
continue
}
if guacd.EnableDrive == key && value == "true" {
storageId := attributes[guacd.DrivePath]
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(guacd.EnableDrive, "true")
configuration.SetParameter(guacd.DriveName, "Filesystem")
configuration.SetParameter(guacd.DrivePath, realPath)
log.Debugf("[%v] 会话 %v:%v 映射目录地址为 %v", s.ID, s.IP, s.Port, realPath)
configuration.SetParameter(guacamole.EnableDrive, "true")
configuration.SetParameter(guacamole.DriveName, "Filesystem")
configuration.SetParameter(guacamole.DrivePath, realPath)
} else {
configuration.SetParameter(key, value)
}
@ -202,7 +203,7 @@ func (api GuacamoleApi) setAssetConfig(attributes map[string]string, s model.Ses
func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
log.Errorf("升级为WebSocket协议失败%v", err.Error())
log.Warn("升级为WebSocket协议失败", log.NamedError("err", err))
return err
}
ctx := context.TODO()
@ -212,12 +213,12 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
if err != nil {
return err
}
if s.Status != constant.Connected {
utils.Disconnect(ws, AssetNotActive, "会话离线")
if s.Status != nt.Connected {
guacamole.Disconnect(ws, AssetNotActive, "会话离线")
return nil
}
connectionId := s.ConnectionId
configuration := guacd.NewConfiguration()
configuration := guacamole.NewConfiguration()
configuration.ConnectionID = connectionId
sessionId = s.ID
configuration.SetParameter("width", strconv.Itoa(s.Width))
@ -225,13 +226,10 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
configuration.SetParameter("dpi", "96")
addr := config.GlobalCfg.Guacd.Hostname + ":" + strconv.Itoa(config.GlobalCfg.Guacd.Port)
asset := fmt.Sprintf("%s:%s", configuration.GetParameter("hostname"), configuration.GetParameter("port"))
log.Debugf("[%v] 新建 guacd 会话, guacd=%v, asset=%v", sessionId, addr, asset)
guacdTunnel, err := guacd.NewTunnel(addr, configuration)
guacdTunnel, err := guacamole.NewTunnel(addr, configuration)
if err != nil {
utils.Disconnect(ws, NewTunnelError, err.Error())
log.Printf("[%v] 建立连接失败: %v", sessionId, err.Error())
guacamole.Disconnect(ws, NewTunnelError, err.Error())
return err
}
@ -246,12 +244,11 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
// 要监控会话
forObsSession := session.GlobalSessionManager.GetById(sessionId)
if forObsSession == nil {
utils.Disconnect(ws, NotFoundSession, "获取会话失败")
guacamole.Disconnect(ws, NotFoundSession, "获取会话失败")
return nil
}
nextSession.ID = utils.UUID()
forObsSession.Observer.Add(nextSession)
log.Debugf("[%v:%v] 观察者[%v]加入会话[%v]", sessionId, connectionId, nextSession.ID, s.ConnectionId)
guacamoleHandler := NewGuacamoleHandler(ws, guacdTunnel)
guacamoleHandler.Start()
@ -260,13 +257,11 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
for {
_, message, err := ws.ReadMessage()
if err != nil {
log.Debugf("[%v:%v] WebSocket已关闭, %v", sessionId, connectionId, err.Error())
// guacdTunnel.Read() 会阻塞所以要先把guacdTunnel客户端关闭才能退出Guacd循环
_ = guacdTunnel.Close()
observerId := nextSession.ID
forObsSession.Observer.Del(observerId)
log.Debugf("[%v:%v] 观察者[%v]退出会话", sessionId, connectionId, observerId)
return nil
}
_, err = guacdTunnel.WriteAndFlush(message)
@ -277,12 +272,12 @@ func (api GuacamoleApi) GuacamoleMonitor(c echo.Context) error {
}
}
func (api GuacamoleApi) setConfig(propertyMap map[string]string, s model.Session, configuration *guacd.Configuration) {
if propertyMap[guacd.EnableRecording] == "true" {
configuration.SetParameter(guacd.RecordingPath, path.Join(config.GlobalCfg.Guacd.Recording, s.ID))
configuration.SetParameter(guacd.CreateRecordingPath, "true")
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(guacd.RecordingPath, "")
configuration.SetParameter(guacamole.RecordingPath, "")
}
configuration.Protocol = s.Protocol
@ -295,18 +290,18 @@ func (api GuacamoleApi) setConfig(propertyMap map[string]string, s model.Session
configuration.SetParameter("ignore-cert", "true")
configuration.SetParameter("create-drive-path", "true")
configuration.SetParameter("resize-method", "reconnect")
configuration.SetParameter(guacd.EnableWallpaper, propertyMap[guacd.EnableWallpaper])
configuration.SetParameter(guacd.EnableTheming, propertyMap[guacd.EnableTheming])
configuration.SetParameter(guacd.EnableFontSmoothing, propertyMap[guacd.EnableFontSmoothing])
configuration.SetParameter(guacd.EnableFullWindowDrag, propertyMap[guacd.EnableFullWindowDrag])
configuration.SetParameter(guacd.EnableDesktopComposition, propertyMap[guacd.EnableDesktopComposition])
configuration.SetParameter(guacd.EnableMenuAnimations, propertyMap[guacd.EnableMenuAnimations])
configuration.SetParameter(guacd.DisableBitmapCaching, propertyMap[guacd.DisableBitmapCaching])
configuration.SetParameter(guacd.DisableOffscreenCaching, propertyMap[guacd.DisableOffscreenCaching])
configuration.SetParameter(guacd.ColorDepth, propertyMap[guacd.ColorDepth])
configuration.SetParameter(guacd.ForceLossless, propertyMap[guacd.ForceLossless])
configuration.SetParameter(guacd.PreConnectionId, propertyMap[guacd.PreConnectionId])
configuration.SetParameter(guacd.PreConnectionBlob, propertyMap[guacd.PreConnectionBlob])
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)
@ -317,11 +312,11 @@ func (api GuacamoleApi) setConfig(propertyMap map[string]string, s model.Session
configuration.SetParameter("password", s.Password)
}
configuration.SetParameter(guacd.FontSize, propertyMap[guacd.FontSize])
configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName])
configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme])
configuration.SetParameter(guacd.Backspace, propertyMap[guacd.Backspace])
configuration.SetParameter(guacd.TerminalType, propertyMap[guacd.TerminalType])
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)
@ -329,17 +324,17 @@ func (api GuacamoleApi) setConfig(propertyMap map[string]string, s model.Session
configuration.SetParameter("username", s.Username)
configuration.SetParameter("password", s.Password)
configuration.SetParameter(guacd.FontSize, propertyMap[guacd.FontSize])
configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName])
configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme])
configuration.SetParameter(guacd.Backspace, propertyMap[guacd.Backspace])
configuration.SetParameter(guacd.TerminalType, propertyMap[guacd.TerminalType])
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(guacd.FontSize, propertyMap[guacd.FontSize])
configuration.SetParameter(guacd.FontName, propertyMap[guacd.FontName])
configuration.SetParameter(guacd.ColorScheme, propertyMap[guacd.ColorScheme])
configuration.SetParameter(guacd.Backspace, propertyMap[guacd.Backspace])
configuration.SetParameter(guacd.TerminalType, propertyMap[guacd.TerminalType])
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:
}

View File

@ -2,22 +2,19 @@ package api
import (
"context"
"next-terminal/server/guacd"
"next-terminal/server/log"
"next-terminal/server/utils"
"next-terminal/server/common/guacamole"
"github.com/gorilla/websocket"
)
type GuacamoleHandler struct {
ws *websocket.Conn
tunnel *guacd.Tunnel
tunnel *guacamole.Tunnel
ctx context.Context
cancel context.CancelFunc
}
func NewGuacamoleHandler(ws *websocket.Conn, tunnel *guacd.Tunnel) *GuacamoleHandler {
func NewGuacamoleHandler(ws *websocket.Conn, tunnel *guacamole.Tunnel) *GuacamoleHandler {
ctx, cancel := context.WithCancel(context.Background())
return &GuacamoleHandler{
ws: ws,
@ -36,7 +33,7 @@ func (r GuacamoleHandler) Start() {
default:
instruction, err := r.tunnel.Read()
if err != nil {
utils.Disconnect(r.ws, TunnelClosed, "远程连接已关闭")
guacamole.Disconnect(r.ws, TunnelClosed, "远程连接已关闭")
return
}
if len(instruction) == 0 {
@ -44,7 +41,6 @@ func (r GuacamoleHandler) Start() {
}
err = r.ws.WriteMessage(websocket.TextMessage, instruction)
if err != nil {
log.Debugf("WebSocket写入失败即将关闭Guacd连接...")
return
}
}

View File

@ -2,7 +2,8 @@ package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -23,7 +24,7 @@ func (api JobApi) JobCreateEndpoint(c echo.Context) error {
}
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := service.JobService.Create(context.TODO(), &item); err != nil {
return err
@ -45,7 +46,7 @@ func (api JobApi) JobPagingEndpoint(c echo.Context) error {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -110,13 +111,17 @@ func (api JobApi) JobGetEndpoint(c echo.Context) error {
func (api JobApi) JobGetLogsEndpoint(c echo.Context) error {
id := c.Param("id")
items, err := repository.JobLogRepository.FindByJobId(context.TODO(), 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, items)
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (api JobApi) JobDeleteLogsEndpoint(c echo.Context) error {

View File

@ -2,6 +2,7 @@ package api
import (
"context"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -26,7 +27,7 @@ func (api LoginLogApi) LoginLogPagingEndpoint(c echo.Context) error {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})

143
server/api/login_policy.go Normal file
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)
}

View File

@ -2,10 +2,11 @@ package api
import (
"context"
"next-terminal/server/constant"
"next-terminal/server/common/nt"
"next-terminal/server/dto"
"next-terminal/server/global/stat"
"next-terminal/server/repository"
"time"
"github.com/labstack/echo/v4"
)
@ -14,21 +15,27 @@ type OverviewApi struct{}
func (api OverviewApi) OverviewCounterEndPoint(c echo.Context) error {
var (
countUser int64
countOnlineSession int64
credential int64
asset int64
totalUser int64
onlineUser int64
countOfflineSession int64
totalAsset int64
activeAsset int64
failLoginCount int64
)
countUser, _ = repository.UserRepository.CountOnlineUser(context.TODO())
countOnlineSession, _ = repository.SessionRepository.CountOnlineSession(context.TODO())
credential, _ = repository.CredentialRepository.Count(context.TODO())
asset, _ = repository.AssetRepository.Count(context.TODO())
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{
User: countUser,
OnlineSession: countOnlineSession,
Credential: credential,
Asset: asset,
TotalUser: totalUser,
OnlineUser: onlineUser,
OfflineSession: countOfflineSession,
TotalAsset: totalAsset,
ActiveAsset: activeAsset,
FailLoginCount: failLoginCount,
}
return Success(c, counter)
@ -43,11 +50,11 @@ func (api OverviewApi) OverviewAssetEndPoint(c echo.Context) error {
kubernetes int64
)
ssh, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.SSH)
rdp, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.RDP)
vnc, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.VNC)
telnet, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.Telnet)
kubernetes, _ = repository.AssetRepository.CountByProtocol(context.TODO(), constant.K8s)
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,
@ -55,14 +62,168 @@ func (api OverviewApi) OverviewAssetEndPoint(c echo.Context) error {
"vnc": vnc,
"telnet": telnet,
"kubernetes": kubernetes,
"all": ssh + rdp + vnc + telnet + kubernetes,
}
return Success(c, m)
}
func (api OverviewApi) OverviewAccessEndPoint(c echo.Context) error {
access, err := repository.SessionRepository.OverviewAccess(context.TODO())
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
}
return Success(c, access)
// 最近一月活跃用户
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)
}

View File

@ -1,51 +0,0 @@
package api
import (
"context"
"next-terminal/server/dto"
"next-terminal/server/repository"
"next-terminal/server/service"
"github.com/labstack/echo/v4"
)
type ResourceSharerApi struct{}
func (api ResourceSharerApi) RSGetSharersEndPoint(c echo.Context) error {
resourceId := c.QueryParam("resourceId")
resourceType := c.QueryParam("resourceType")
userId := c.QueryParam("userId")
userGroupId := c.QueryParam("userGroupId")
userIds, err := repository.ResourceSharerRepository.Find(context.TODO(), resourceId, resourceType, userId, userGroupId)
if err != nil {
return err
}
return Success(c, userIds)
}
func (api ResourceSharerApi) ResourceRemoveByUserIdAssignEndPoint(c echo.Context) error {
var ru dto.RU
if err := c.Bind(&ru); err != nil {
return err
}
if err := repository.ResourceSharerRepository.DeleteByUserIdAndResourceTypeAndResourceIdIn(context.TODO(), ru.UserGroupId, ru.UserId, ru.ResourceType, ru.ResourceIds); err != nil {
return err
}
return Success(c, "")
}
func (api ResourceSharerApi) ResourceAddByUserIdAssignEndPoint(c echo.Context) error {
var ru dto.RU
if err := c.Bind(&ru); err != nil {
return err
}
if err := service.UserService.AddSharerResources(context.TODO(), ru.UserGroupId, ru.UserId, ru.StrategyId, ru.ResourceType, ru.ResourceIds); err != nil {
return err
}
return Success(c, "")
}

100
server/api/role.go Normal file
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())
}

View File

@ -2,7 +2,7 @@ package api
import (
"context"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -54,7 +54,7 @@ func (api SecurityApi) SecurityPagingEndpoint(c echo.Context) error {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})

View File

@ -13,9 +13,10 @@ import (
"strconv"
"strings"
"next-terminal/server/constant"
"next-terminal/server/common"
"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"
@ -44,10 +45,10 @@ func (api SessionApi) SessionPagingEndpoint(c echo.Context) error {
}
for i := 0; i < len(items); i++ {
if status == constant.Disconnected && len(items[i].Recording) > 0 {
if status == nt.Disconnected && len(items[i].Recording) > 0 {
var recording string
if items[i].Mode == constant.Native || items[i].Mode == constant.Terminal {
if items[i].Mode == nt.Native || items[i].Mode == nt.Terminal {
recording = items[i].Recording
} else {
recording = items[i].Recording + "/recording"
@ -63,7 +64,7 @@ func (api SessionApi) SessionPagingEndpoint(c echo.Context) error {
}
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -71,7 +72,7 @@ func (api SessionApi) SessionPagingEndpoint(c echo.Context) error {
func (api SessionApi) SessionDeleteEndpoint(c echo.Context) error {
sessionIds := strings.Split(c.Param("id"), ",")
err := repository.SessionRepository.DeleteByIds(context.TODO(), sessionIds)
err := service.SessionService.DeleteByIds(context.TODO(), sessionIds)
if err != nil {
return err
}
@ -115,8 +116,8 @@ func (api SessionApi) SessionConnectEndpoint(c echo.Context) error {
s := model.Session{}
s.ID = sessionId
s.Status = constant.Connected
s.ConnectedTime = utils.NowJsonTime()
s.Status = nt.Connected
s.ConnectedTime = common.NowJsonTime()
if err := repository.SessionRepository.UpdateById(context.TODO(), &s, sessionId); err != nil {
return err
@ -170,10 +171,10 @@ func (api SessionApi) SessionCreateEndpoint(c echo.Context) error {
assetId := c.QueryParam("assetId")
mode := c.QueryParam("mode")
if mode == constant.Native {
mode = constant.Native
if mode == nt.Native {
mode = nt.Native
} else {
mode = constant.Guacd
mode = nt.Guacd
}
user, _ := GetCurrentAccount(c)
@ -221,6 +222,10 @@ func (api SessionApi) SessionUploadEndpoint(c echo.Context) error {
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 {
@ -313,6 +318,11 @@ func (api SessionApi) SessionDownloadEndpoint(c echo.Context) error {
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 {
@ -360,7 +370,6 @@ func (api SessionApi) SessionLsEndpoint(c echo.Context) error {
if nextSession.NextTerminal.SftpClient == nil {
sftpClient, err := sftp.NewClient(nextSession.NextTerminal.SshClient)
if err != nil {
log.Errorf("创建sftp客户端失败%v", err.Error())
return err
}
nextSession.NextTerminal.SftpClient = sftpClient
@ -374,18 +383,13 @@ func (api SessionApi) SessionLsEndpoint(c echo.Context) error {
var files = make([]service.File, 0)
for i := range fileInfos {
// 忽略隐藏文件
if strings.HasPrefix(fileInfos[i].Name(), ".") {
continue
}
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: utils.NewJsonTime(fileInfos[i].ModTime()),
ModTime: common.NewJsonTime(fileInfos[i].ModTime()),
Size: fileInfos[i].Size(),
}
@ -415,6 +419,11 @@ func (api SessionApi) SessionMkDirEndpoint(c echo.Context) error {
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 {
@ -445,6 +454,11 @@ func (api SessionApi) SessionRmEndpoint(c echo.Context) error {
}
// 文件夹或者文件
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 {
@ -502,6 +516,11 @@ func (api SessionApi) SessionRenameEndpoint(c echo.Context) error {
}
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 {
@ -533,15 +552,13 @@ func (api SessionApi) SessionRecordingEndpoint(c echo.Context) error {
}
var recording string
if s.Mode == constant.Native || s.Mode == constant.Terminal {
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})
log.Debugf("读取录屏文件:%v,是否存在: %v, 是否为文件: %v", recording, utils.FileExists(recording), utils.IsFile(recording))
http.ServeFile(c.Response(), c.Request(), recording)
return nil
}
@ -570,8 +587,8 @@ func (api SessionApi) SessionStatsEndpoint(c echo.Context) error {
if nextSession == nil {
return errors.New("获取会话失败")
}
sshClient := nextSession.NextTerminal.SshClient
stats, err := GetAllStats(sshClient)
stats, err := GetAllStats(nextSession)
if err != nil {
return err
}

View File

@ -1,6 +0,0 @@
package api
func DealCommand(enterKeys []rune) {
println(string(enterKeys))
}

View File

@ -3,6 +3,8 @@ package api
import (
"bufio"
"fmt"
"next-terminal/server/common/taskrunner"
"next-terminal/server/global/session"
"strconv"
"strings"
"time"
@ -70,40 +72,56 @@ type Stat struct {
CPU CPU `json:"cpu"`
}
func GetAllStats(client *ssh.Client) (*Stat, error) {
func GetAllStats(nextSession *session.Session) (*Stat, error) {
client := nextSession.NextTerminal.SshClient
start := time.Now()
stats := &Stat{}
if err := getUptime(client, stats); err != nil {
return nil, err
stats := &Stat{
Uptime: nextSession.Uptime,
Hostname: nextSession.Hostname,
}
if err := getHostname(client, stats); err != nil {
return nil, err
if stats.Uptime == 0 {
if err := getUptime(client, stats); err != nil {
return nil, err
}
nextSession.Uptime = stats.Uptime
}
if err := getLoad(client, stats); err != nil {
return nil, err
}
if err := getMem(client, stats); err != nil {
return nil, err
}
if err := getFileSystems(client, stats); err != nil {
return nil, err
}
if err := getInterfaces(client, stats); err != nil {
return nil, err
}
if err := getInterfaceInfo(client, stats); err != nil {
return nil, err
}
if err := getCPU(client, stats); err != nil {
return nil, err
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")
defer utils.TimeWatcher("getHostname")
hostname, err := utils.RunCommand(client, "/bin/hostname -f")
if err != nil {
return
@ -113,7 +131,7 @@ func getHostname(client *ssh.Client, stat *Stat) (err error) {
}
func getUptime(client *ssh.Client, stat *Stat) (err error) {
//defer utils.TimeWatcher("getUptime")
defer utils.TimeWatcher("getUptime")
uptime, err := utils.RunCommand(client, "/bin/cat /proc/uptime")
if err != nil {
return
@ -132,7 +150,7 @@ func getUptime(client *ssh.Client, stat *Stat) (err error) {
}
func getLoad(client *ssh.Client, stat *Stat) (err error) {
//defer utils.TimeWatcher("getLoad")
defer utils.TimeWatcher("getLoad")
line, err := utils.RunCommand(client, "/bin/cat /proc/loadavg")
if err != nil {
return
@ -154,7 +172,7 @@ func getLoad(client *ssh.Client, stat *Stat) (err error) {
}
func getMem(client *ssh.Client, stat *Stat) (err error) {
//defer utils.TimeWatcher("getMem")
defer utils.TimeWatcher("getMem")
lines, err := utils.RunCommand(client, "/bin/cat /proc/meminfo")
if err != nil {
return
@ -192,7 +210,7 @@ func getMem(client *ssh.Client, stat *Stat) (err error) {
}
func getFileSystems(client *ssh.Client, stat *Stat) (err error) {
//defer utils.TimeWatcher("getFileSystems")
defer utils.TimeWatcher("getFileSystems")
lines, err := utils.RunCommand(client, "/bin/df -B1")
if err != nil {
return
@ -228,7 +246,7 @@ func getFileSystems(client *ssh.Client, stat *Stat) (err error) {
}
func getInterfaces(client *ssh.Client, stats *Stat) (err error) {
//defer utils.TimeWatcher("getInterfaces")
defer utils.TimeWatcher("getInterfaces")
var lines string
lines, err = utils.RunCommand(client, "/bin/ip -o addr")
if err != nil {
@ -273,16 +291,16 @@ func getInterfaces(client *ssh.Client, stats *Stat) (err error) {
}
func getInterfaceInfo(client *ssh.Client, stats *Stat) (err error) {
//defer utils.TimeWatcher("getInterfaceInfo")
lines, err := utils.RunCommand(client, "/bin/cat /proc/net/dev")
if err != nil {
return
}
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()
@ -345,7 +363,7 @@ func parseCPUFields(fields []string, stat *cpuRaw) {
var preCPU cpuRaw
func getCPU(client *ssh.Client, stats *Stat) (err error) {
//defer utils.TimeWatcher("getCPU")
defer utils.TimeWatcher("getCPU")
lines, err := utils.RunCommand(client, "/bin/cat /proc/stat")
if err != nil {
return

View File

@ -3,12 +3,14 @@ package api
import (
"context"
"errors"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"next-terminal/server/common/nt"
"os"
"path"
"strconv"
"strings"
"next-terminal/server/constant"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
@ -44,7 +46,7 @@ func (api StorageApi) StoragePagingEndpoint(c echo.Context) error {
}
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -59,7 +61,7 @@ func (api StorageApi) StorageCreateEndpoint(c echo.Context) error {
account, _ := GetCurrentAccount(c)
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
item.Owner = account.ID
// 创建对应的目录文件夹
drivePath := service.StorageService.GetBaseDrivePath()
@ -147,7 +149,7 @@ func (api StorageApi) PermissionCheck(c echo.Context, id string) error {
return err
}
account, _ := GetCurrentAccount(c)
if account.Type != constant.TypeAdmin {
if account.Type != nt.TypeAdmin {
if storage.Owner != account.ID {
return errors.New("您没有权限访问此地址 :(")
}

48
server/api/storage_log.go Normal file
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)
}

View File

@ -2,7 +2,8 @@ package api
import (
"context"
"next-terminal/server/common"
"next-terminal/server/common/maps"
"strconv"
"strings"
@ -36,7 +37,7 @@ func (api StrategyApi) StrategyPagingEndpoint(c echo.Context) error {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -48,7 +49,7 @@ func (api StrategyApi) StrategyCreateEndpoint(c echo.Context) error {
return err
}
item.ID = utils.UUID()
item.Created = utils.NowJsonTime()
item.Created = common.NowJsonTime()
if err := repository.StrategyRepository.Create(context.TODO(), &item); err != nil {
return err
@ -80,3 +81,12 @@ func (api StrategyApi) StrategyUpdateEndpoint(c echo.Context) error {
}
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)
}

83
server/api/tenant.go Normal file
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, "")
}

View File

@ -4,20 +4,18 @@ import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"next-terminal/server/common/nt"
"path"
"strconv"
"next-terminal/server/common/guacamole"
"next-terminal/server/common/term"
"next-terminal/server/config"
"next-terminal/server/constant"
"next-terminal/server/dto"
"next-terminal/server/global/session"
"next-terminal/server/guacd"
"next-terminal/server/log"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/term"
"next-terminal/server/utils"
"github.com/gorilla/websocket"
@ -38,7 +36,6 @@ type WebTerminalApi struct {
func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
log.Errorf("升级为WebSocket协议失败%v", err.Error())
return err
}
@ -86,7 +83,7 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
recording := ""
var isRecording = false
property, err := repository.PropertyRepository.FindByName(ctx, guacd.EnableRecording)
property, err := repository.PropertyRepository.FindByName(ctx, guacamole.EnableRecording)
if err == nil && property.Value == "true" {
isRecording = true
}
@ -102,8 +99,8 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
var xterm = "xterm-256color"
var nextTerminal *term.NextTerminal
if "true" == attributes[constant.SocksProxyEnable] {
nextTerminal, err = term.NewNextTerminalUseSocks(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true, attributes[constant.SocksProxyHost], attributes[constant.SocksProxyPort], attributes[constant.SocksProxyUsername], attributes[constant.SocksProxyPassword])
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)
}
@ -120,20 +117,19 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
return err
}
sess := model.Session{
sessionForUpdate := model.Session{
ConnectionId: sessionId,
Width: cols,
Height: rows,
Status: constant.Connecting,
Status: nt.Connecting,
Recording: recording,
}
if sess.Recording == "" {
if sessionForUpdate.Recording == "" {
// 未录屏时无需审计
sess.Reviewed = true
sessionForUpdate.Reviewed = true
}
// 创建新会话
log.Debugf("创建新会话 %v", sess.ConnectionId)
if err := repository.SessionRepository.UpdateById(ctx, &sess, sessionId); err != nil {
if err := repository.SessionRepository.UpdateById(ctx, &sessionForUpdate, sessionId); err != nil {
return err
}
@ -152,7 +148,7 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
}
session.GlobalSessionManager.Add(nextSession)
termHandler := NewTermHandler(sessionId, isRecording, ws, nextTerminal)
termHandler := NewTermHandler(s.Creator, s.AssetId, sessionId, isRecording, ws, nextTerminal)
termHandler.Start()
defer termHandler.Stop()
@ -160,14 +156,12 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
_, message, err := ws.ReadMessage()
if err != nil {
// web socket会话关闭后主动关闭ssh会话
log.Debugf("WebSocket已关闭")
service.SessionService.CloseSessionById(sessionId, Normal, "用户正常退出")
break
}
msg, err := dto.ParseMessage(string(message))
if err != nil {
log.Warnf("消息解码失败: %v, 原始字符串:%v", err, string(message))
continue
}
@ -175,31 +169,28 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
case Resize:
decodeString, err := base64.StdEncoding.DecodeString(msg.Content)
if err != nil {
log.Warnf("Base64解码失败: %v原始字符串%v", err, msg.Content)
continue
}
var winSize dto.WindowSize
err = json.Unmarshal(decodeString, &winSize)
if err != nil {
log.Warnf("解析SSH会话窗口大小失败: %v原始字符串%v", err, msg.Content)
continue
}
if err := nextTerminal.WindowChange(winSize.Rows, winSize.Cols); err != nil {
log.Warnf("更改SSH会话窗口大小失败: %v", err)
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 := nextTerminal.Write(input)
err := termHandler.Write(input)
if err != nil {
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
}
case Ping:
_, _, err := nextTerminal.SshClient.Conn.SendRequest("helloworld1024@foxmail.com", true, nil)
err := termHandler.SendRequest()
if err != nil {
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
} else {
_ = termHandler.WriteMessage(dto.NewMessage(Ping, ""))
_ = termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, ""))
}
}
@ -210,7 +201,6 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
func (api WebTerminalApi) SshMonitorEndpoint(c echo.Context) error {
ws, err := UpGrader.Upgrade(c.Response().Writer, c.Request(), nil)
if err != nil {
log.Errorf("升级为WebSocket协议失败%v", err.Error())
return err
}
@ -238,12 +228,10 @@ func (api WebTerminalApi) SshMonitorEndpoint(c echo.Context) error {
WebSocket: ws,
}
nextSession.Observer.Add(obSession)
log.Debugf("会话 %v 观察者 %v 进入", sessionId, obId)
for {
_, _, err := ws.ReadMessage()
if err != nil {
log.Debugf("会话 %v 观察者 %v 退出", sessionId, obId)
nextSession.Observer.Del(obId)
break
}
@ -253,16 +241,16 @@ func (api WebTerminalApi) SshMonitorEndpoint(c echo.Context) error {
func (api WebTerminalApi) permissionCheck(c echo.Context, assetId string) error {
user, _ := GetCurrentAccount(c)
if constant.TypeUser == user.Type {
// 检测是否有访问权限
assetIds, err := repository.ResourceSharerRepository.FindAssetIdsByUserId(context.TODO(), user.ID)
if err != nil {
return err
}
if !utils.Contains(assetIds, assetId) {
return errors.New("您没有权限访问此资产")
}
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
}

View File

@ -1,16 +1,16 @@
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"
"next-terminal/server/term"
"github.com/gorilla/websocket"
)
type TermHandler struct {
@ -23,11 +23,13 @@ type TermHandler struct {
dataChan chan rune
tick *time.Ticker
mutex sync.Mutex
buf bytes.Buffer
}
func NewTermHandler(sessionId string, isRecording bool, ws *websocket.Conn, nextTerminal *term.NextTerminal) *TermHandler {
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,
@ -46,6 +48,7 @@ func (r *TermHandler) Start() {
}
func (r *TermHandler) Stop() {
// 会话结束时记录最后一个命令
r.tick.Stop()
r.cancel()
}
@ -68,38 +71,50 @@ func (r *TermHandler) readFormTunnel() {
}
func (r *TermHandler) writeToWebsocket() {
var buf []byte
for {
select {
case <-r.ctx.Done():
return
case <-r.tick.C:
if len(buf) > 0 {
s := string(buf)
if err := r.WriteMessage(dto.NewMessage(Data, s)); err != nil {
return
}
// 录屏
if r.isRecording {
_ = r.nextTerminal.Recorder.WriteData(s)
}
// 监控
SendObData(r.sessionId, s)
buf = []byte{}
s := r.buf.String()
if err := r.SendMessageToWebSocket(dto.NewMessage(Data, s)); err != nil {
return
}
// 录屏
if r.isRecording {
_ = 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)
buf = append(buf, p...)
r.buf.Write(p)
} else {
buf = append(buf, []byte("@")...)
r.buf.Write([]byte("@"))
}
}
}
}
func (r *TermHandler) WriteMessage(msg dto.Message) error {
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
}

View File

@ -1,66 +0,0 @@
package main
import (
"fmt"
"strings"
"github.com/manifoldco/promptui"
)
type pepper struct {
Name string
HeatUnit int
Peppers int
}
func main() {
peppers := []pepper{
{Name: "Bell Pepper", HeatUnit: 0, Peppers: 0},
{Name: "Banana Pepper", HeatUnit: 100, Peppers: 1},
{Name: "Poblano", HeatUnit: 1000, Peppers: 2},
{Name: "Jalapeño", HeatUnit: 3500, Peppers: 3},
{Name: "Aleppo", HeatUnit: 10000, Peppers: 4},
{Name: "Tabasco", HeatUnit: 30000, Peppers: 5},
{Name: "Malagueta", HeatUnit: 50000, Peppers: 6},
{Name: "Habanero", HeatUnit: 100000, Peppers: 7},
{Name: "Red Savina Habanero", HeatUnit: 350000, Peppers: 8},
{Name: "Dragons Breath", HeatUnit: 855000, Peppers: 9},
}
templates := &promptui.SelectTemplates{
Label: "{{ . }}?",
Active: "\U0001F336 {{ .Name | cyan }} ({{ .HeatUnit | red }})",
Inactive: " {{ .Name | cyan }} ({{ .HeatUnit | red }})",
Selected: "\U0001F336 {{ .Name | red | cyan }}",
Details: `
--------- Pepper ----------
{{ "Name:" | faint }} {{ .Name }}/
{{ "Heat Unit:" | faint }} {{ .HeatUnit }}
{{ "Peppers:" | faint }} {{ .Peppers }}`,
}
searcher := func(input string, index int) bool {
pepper := peppers[index]
name := strings.Replace(strings.ToLower(pepper.Name), " ", "", -1)
input = strings.Replace(strings.ToLower(input), " ", "", -1)
return strings.Contains(name, input)
}
prompt := promptui.Select{
Label: "Spicy Level",
Items: peppers,
Templates: templates,
Size: 4,
Searcher: searcher,
}
i, _, err := prompt.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
return
}
fmt.Printf("You choose number %d: %s\n", i+1, peppers[i].Name)
}

View File

@ -2,21 +2,19 @@ package api
import (
"context"
"fmt"
"next-terminal/server/common/maps"
"strconv"
"strings"
"github.com/labstack/echo/v4"
"next-terminal/server/model"
"next-terminal/server/repository"
"next-terminal/server/service"
"next-terminal/server/utils"
"github.com/labstack/echo/v4"
)
type UserApi struct{}
func (userApi UserApi) UserCreateEndpoint(c echo.Context) (err error) {
func (userApi UserApi) CreateEndpoint(c echo.Context) (err error) {
var item model.User
if err := c.Bind(&item); err != nil {
return err
@ -29,7 +27,7 @@ func (userApi UserApi) UserCreateEndpoint(c echo.Context) (err error) {
return Success(c, item)
}
func (userApi UserApi) UserPagingEndpoint(c echo.Context) error {
func (userApi UserApi) PagingEndpoint(c echo.Context) error {
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
username := c.QueryParam("username")
@ -38,25 +36,26 @@ func (userApi UserApi) UserPagingEndpoint(c echo.Context) error {
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, order, field)
items, total, err := repository.UserRepository.Find(context.TODO(), pageIndex, pageSize, username, nickname, mail, online, "", order, field)
if err != nil {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
}
func (userApi UserApi) UserUpdateEndpoint(c echo.Context) error {
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")
}
//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 {
@ -70,7 +69,7 @@ func (userApi UserApi) UserUpdateEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (userApi UserApi) UserUpdateStatusEndpoint(c echo.Context) error {
func (userApi UserApi) UpdateStatusEndpoint(c echo.Context) error {
id := c.Param("id")
status := c.QueryParam("status")
account, _ := GetCurrentAccount(c)
@ -85,7 +84,7 @@ func (userApi UserApi) UserUpdateStatusEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (userApi UserApi) UserDeleteEndpoint(c echo.Context) error {
func (userApi UserApi) DeleteEndpoint(c echo.Context) error {
ids := c.Param("id")
account, found := GetCurrentAccount(c)
if !found {
@ -105,10 +104,10 @@ func (userApi UserApi) UserDeleteEndpoint(c echo.Context) error {
return Success(c, nil)
}
func (userApi UserApi) UserGetEndpoint(c echo.Context) error {
func (userApi UserApi) GetEndpoint(c echo.Context) error {
id := c.Param("id")
item, err := repository.UserRepository.FindById(context.TODO(), id)
item, err := service.UserService.FindById(id)
if err != nil {
return err
}
@ -116,49 +115,41 @@ func (userApi UserApi) UserGetEndpoint(c echo.Context) error {
return Success(c, item)
}
func (userApi UserApi) UserChangePasswordEndpoint(c echo.Context) error {
func (userApi UserApi) ChangePasswordEndpoint(c echo.Context) error {
id := c.Param("id")
password := c.FormValue("password")
if password == "" {
return Fail(c, -1, "请输入密码")
}
user, err := repository.UserRepository.FindById(context.TODO(), id)
if err != nil {
ids := strings.Split(id, ",")
if err := service.UserService.ChangePassword(ids, password); err != nil {
return err
}
passwd, err := utils.Encoder.Encode([]byte(password))
if err != nil {
return err
}
u := &model.User{
Password: string(passwd),
ID: id,
}
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
return err
}
if user.Mail != "" {
subject := "密码修改通知"
text := fmt.Sprintf(`您好,%s。
管理员已将你的密码修改为:%s。
`, user.Username, password)
go service.MailService.SendMail(user.Mail, subject, text)
}
return Success(c, "")
}
func (userApi UserApi) UserResetTotpEndpoint(c echo.Context) error {
func (userApi UserApi) ResetTotpEndpoint(c echo.Context) error {
id := c.Param("id")
u := &model.User{
TOTPSecret: "-",
ID: id,
}
if err := repository.UserRepository.Update(context.TODO(), u); err != nil {
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)
}

View File

@ -2,6 +2,8 @@ package api
import (
"context"
"next-terminal/server/common/maps"
"next-terminal/server/model"
"strconv"
"strings"
@ -15,7 +17,7 @@ import (
type UserGroupApi struct{}
func (userGroupApi UserGroupApi) UserGroupCreateEndpoint(c echo.Context) error {
var item dto.UserGroup
var item model.UserGroup
if err := c.Bind(&item); err != nil {
return err
}
@ -40,7 +42,7 @@ func (userGroupApi UserGroupApi) UserGroupPagingEndpoint(c echo.Context) error {
return err
}
return Success(c, Map{
return Success(c, maps.Map{
"total": total,
"items": items,
})
@ -49,7 +51,7 @@ func (userGroupApi UserGroupApi) UserGroupPagingEndpoint(c echo.Context) error {
func (userGroupApi UserGroupApi) UserGroupUpdateEndpoint(c echo.Context) error {
id := c.Param("id")
var item dto.UserGroup
var item model.UserGroup
if err := c.Bind(&item); err != nil {
return err
}
@ -82,7 +84,7 @@ func (userGroupApi UserGroupApi) UserGroupGetEndpoint(c echo.Context) error {
return err
}
members, err := repository.UserGroupMemberRepository.FindUserIdsByUserGroupId(context.TODO(), id)
members, err := repository.UserGroupMemberRepository.FindByUserGroupId(context.TODO(), id)
if err != nil {
return err
}
@ -90,8 +92,17 @@ func (userGroupApi UserGroupApi) UserGroupGetEndpoint(c echo.Context) error {
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)
}

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)
}

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
}