feat: 完善日志审计功能
- 实现文件系统日志(FilesystemLog)记录文件管理器操作 - 实现操作日志(OperationLog)记录用户操作行为 - 实现数据库SQL日志(DatabaseSQLLog)模型和API - 实现SSH会话命令记录(SessionCommand)含命令输出和风险等级 - 添加IP提取服务支持X-Real-IP和X-Forwarded-For - 添加日志自动清理功能 - 修复ProFormSwitch required验证问题 - 修复设置页面默认值问题 - 修复文件上传错误检测逻辑 - 修复资产树key前缀问题 - 添加VNC/RDP设置默认值 - 修复文件管理标题翻译
This commit is contained in:
@@ -30,7 +30,8 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
// 存储登录失败次数信息
|
||||
loginFailCountKey := c.RealIP() + loginAccount.Username
|
||||
clientIP := service.PropertyService.GetClientIP(c)
|
||||
loginFailCountKey := clientIP + loginAccount.Username
|
||||
v, ok := cache.LoginFailedKeyManager.Get(loginFailCountKey)
|
||||
if !ok {
|
||||
v = 1
|
||||
@@ -49,7 +50,7 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
|
||||
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 {
|
||||
if err := service.UserService.SaveLoginLog(clientIP, c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||
return err
|
||||
}
|
||||
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||
@@ -63,7 +64,7 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
|
||||
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 {
|
||||
if err := service.UserService.SaveLoginLog(clientIP, c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "账号或密码不正确"); err != nil {
|
||||
return err
|
||||
}
|
||||
return FailWithData(c, -1, "您输入的账号或密码不正确", count)
|
||||
@@ -78,7 +79,7 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
|
||||
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 {
|
||||
if err := service.UserService.SaveLoginLog(clientIP, c.Request().UserAgent(), loginAccount.Username, false, loginAccount.Remember, "", "双因素认证授权码不正确"); err != nil {
|
||||
return err
|
||||
}
|
||||
return FailWithData(c, -1, "您输入双因素认证授权码不正确", count)
|
||||
@@ -86,12 +87,12 @@ func (api AccountApi) LoginEndpoint(c echo.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
token, err := api.LoginSuccess(loginAccount, user, c.RealIP())
|
||||
token, err := api.LoginSuccess(loginAccount, user, clientIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 保存登录日志
|
||||
if err := service.UserService.SaveLoginLog(c.RealIP(), c.Request().UserAgent(), loginAccount.Username, true, loginAccount.Remember, token, ""); err != nil {
|
||||
if err := service.UserService.SaveLoginLog(clientIP, c.Request().UserAgent(), loginAccount.Username, true, loginAccount.Remember, token, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,21 @@ import (
|
||||
|
||||
type AssetApi struct{}
|
||||
|
||||
func recordOperationLog(c echo.Context, action, content, status, errorMessage string) {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
clientIP := service.PropertyService.GetClientIP(c)
|
||||
_ = service.OperationLogService.Record(context.TODO(), service.OperationLogParams{
|
||||
AccountId: account.ID,
|
||||
AccountName: account.Username,
|
||||
Action: action,
|
||||
Content: content,
|
||||
IP: clientIP,
|
||||
Status: status,
|
||||
ErrorMessage: errorMessage,
|
||||
UserAgent: c.Request().UserAgent(),
|
||||
})
|
||||
}
|
||||
|
||||
func (assetApi AssetApi) AssetCreateEndpoint(c echo.Context) error {
|
||||
m := maps.Map{}
|
||||
if err := c.Bind(&m); err != nil {
|
||||
@@ -30,10 +45,13 @@ func (assetApi AssetApi) AssetCreateEndpoint(c echo.Context) error {
|
||||
account, _ := GetCurrentAccount(c)
|
||||
m["owner"] = account.ID
|
||||
|
||||
assetName, _ := m["name"].(string)
|
||||
if _, err := service.AssetService.Create(context.TODO(), m); err != nil {
|
||||
recordOperationLog(c, "asset-add", "创建资产: "+assetName, "failed", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
recordOperationLog(c, "asset-add", "创建资产: "+assetName, "success", "")
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -167,9 +185,12 @@ func (assetApi AssetApi) AssetUpdateEndpoint(c echo.Context) error {
|
||||
if err := c.Bind(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
assetName, _ := m["name"].(string)
|
||||
if err := service.AssetService.UpdateById(id, m); err != nil {
|
||||
recordOperationLog(c, "asset-edit", "更新资产: "+assetName, "failed", err.Error())
|
||||
return err
|
||||
}
|
||||
recordOperationLog(c, "asset-edit", "更新资产: "+assetName, "success", "")
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -178,10 +199,12 @@ func (assetApi AssetApi) AssetDeleteEndpoint(c echo.Context) error {
|
||||
split := strings.Split(id, ",")
|
||||
for i := range split {
|
||||
if err := service.AssetService.DeleteById(split[i]); err != nil {
|
||||
recordOperationLog(c, "asset-del", "删除资产: "+id, "failed", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
recordOperationLog(c, "asset-del", "删除资产: "+id, "success", "")
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ func buildAssetTree(assets []model.Asset, groups []model.AssetGroup, groupId str
|
||||
node := maps.Map{
|
||||
"id": g.ID,
|
||||
"name": g.Name,
|
||||
"key": g.ID,
|
||||
"key": "group_" + g.ID,
|
||||
"title": g.Name,
|
||||
"value": g.ID,
|
||||
}
|
||||
@@ -144,7 +144,7 @@ func buildAssetTree(assets []model.Asset, groups []model.AssetGroup, groupId str
|
||||
nodes = append(nodes, maps.Map{
|
||||
"id": a.ID,
|
||||
"name": a.Name,
|
||||
"key": a.ID,
|
||||
"key": "asset_" + a.ID,
|
||||
"title": a.Name,
|
||||
"value": a.ID,
|
||||
"isLeaf": true,
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"next-terminal/server/common"
|
||||
"next-terminal/server/common/maps"
|
||||
"next-terminal/server/global/session"
|
||||
"next-terminal/server/log"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/utils"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
@@ -17,6 +22,28 @@ import (
|
||||
|
||||
type FileSystemApi struct{}
|
||||
|
||||
func (api FileSystemApi) recordFilesystemLog(ctx context.Context, sessionId, action, fileName string) {
|
||||
sess, err := repository.SessionRepository.FindById(ctx, sessionId)
|
||||
if err != nil {
|
||||
log.Error("Failed to find session for filesystem log", log.NamedError("err", err))
|
||||
return
|
||||
}
|
||||
|
||||
filesystemLog := &model.FilesystemLog{
|
||||
ID: utils.UUID(),
|
||||
AssetId: sess.AssetId,
|
||||
SessionId: sessionId,
|
||||
UserId: sess.Creator,
|
||||
Action: action,
|
||||
FileName: fileName,
|
||||
Created: common.NowJsonTime(),
|
||||
}
|
||||
|
||||
if err := repository.FilesystemLogRepository.Create(ctx, filesystemLog); err != nil {
|
||||
log.Error("Failed to create filesystem log", log.NamedError("err", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (api FileSystemApi) LsEndpoint(c echo.Context) error {
|
||||
sessionId := c.Param("id")
|
||||
dir := c.QueryParam("dir")
|
||||
@@ -84,6 +111,8 @@ func (api FileSystemApi) MkdirEndpoint(c echo.Context) error {
|
||||
return Fail(c, -1, "failed to create directory: "+err.Error())
|
||||
}
|
||||
|
||||
go api.recordFilesystemLog(context.TODO(), sessionId, "mkdir", dir)
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -110,6 +139,8 @@ func (api FileSystemApi) TouchEndpoint(c echo.Context) error {
|
||||
}
|
||||
f.Close()
|
||||
|
||||
go api.recordFilesystemLog(context.TODO(), sessionId, "touch", filename)
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -150,6 +181,8 @@ func (api FileSystemApi) RmEndpoint(c echo.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
go api.recordFilesystemLog(context.TODO(), sessionId, "rm", filename)
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -200,6 +233,8 @@ func (api FileSystemApi) RenameEndpoint(c echo.Context) error {
|
||||
return Fail(c, -1, "failed to rename: "+err.Error())
|
||||
}
|
||||
|
||||
go api.recordFilesystemLog(context.TODO(), sessionId, "rename", oldName+" -> "+newName)
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -238,6 +273,8 @@ func (api FileSystemApi) EditEndpoint(c echo.Context) error {
|
||||
return Fail(c, -1, "failed to write file: "+err.Error())
|
||||
}
|
||||
|
||||
go api.recordFilesystemLog(context.TODO(), sessionId, "edit", req.Filename)
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -269,6 +306,8 @@ func (api FileSystemApi) ReadEndpoint(c echo.Context) error {
|
||||
return Fail(c, -1, "failed to read file: "+err.Error())
|
||||
}
|
||||
|
||||
go api.recordFilesystemLog(context.TODO(), sessionId, "read", filename)
|
||||
|
||||
return Success(c, maps.Map{
|
||||
"content": content,
|
||||
"path": filename,
|
||||
@@ -322,6 +361,8 @@ func (api FileSystemApi) ChmodEndpoint(c echo.Context) error {
|
||||
return Fail(c, -1, "failed to chmod: "+err.Error())
|
||||
}
|
||||
|
||||
go api.recordFilesystemLog(context.TODO(), sessionId, "chmod", filename+" ("+modeStr+")")
|
||||
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -369,6 +410,8 @@ func (api FileSystemApi) DownloadEndpoint(c echo.Context) error {
|
||||
return Fail(c, -1, "failed to stat file: "+err.Error())
|
||||
}
|
||||
|
||||
go api.recordFilesystemLog(context.TODO(), sessionId, "download", filename)
|
||||
|
||||
c.Response().Header().Set("Content-Disposition", "attachment; filename="+path.Base(filename))
|
||||
c.Response().Header().Set("Content-Type", "application/octet-stream")
|
||||
c.Response().Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10))
|
||||
@@ -425,6 +468,8 @@ func (api FileSystemApi) UploadEndpoint(c echo.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
go api.recordFilesystemLog(context.TODO(), sessionId, "upload", destPath)
|
||||
|
||||
return Success(c, maps.Map{
|
||||
"path": destPath,
|
||||
"size": file.Size,
|
||||
|
||||
+17
-30
@@ -65,10 +65,10 @@ func (api PortalApi) AssetsTreeEndpoint(c echo.Context) error {
|
||||
tree := make([]maps.Map, 0)
|
||||
for _, g := range groups {
|
||||
node := maps.Map{
|
||||
"key": g.ID,
|
||||
"key": "group_" + g.ID,
|
||||
"title": g.Name,
|
||||
"isLeaf": false,
|
||||
"children": buildPortalAssetChildren(items, g.ID, keyword),
|
||||
"children": []interface{}{},
|
||||
}
|
||||
tree = append(tree, node)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func (api PortalApi) AssetsTreeEndpoint(c echo.Context) error {
|
||||
continue
|
||||
}
|
||||
node := maps.Map{
|
||||
"key": a.ID,
|
||||
"key": "asset_" + a.ID,
|
||||
"title": a.Name,
|
||||
"isLeaf": true,
|
||||
"extra": maps.Map{
|
||||
@@ -93,29 +93,6 @@ func (api PortalApi) AssetsTreeEndpoint(c echo.Context) error {
|
||||
return Success(c, tree)
|
||||
}
|
||||
|
||||
func buildPortalAssetChildren(assets []model.Asset, groupId, keyword string) []maps.Map {
|
||||
children := make([]maps.Map, 0)
|
||||
for _, a := range assets {
|
||||
if keyword != "" && !containsKeyword(a.Name, keyword) {
|
||||
continue
|
||||
}
|
||||
node := maps.Map{
|
||||
"key": a.ID,
|
||||
"title": a.Name,
|
||||
"isLeaf": true,
|
||||
"extra": maps.Map{
|
||||
"protocol": a.Protocol,
|
||||
"logo": "",
|
||||
"status": "unknown",
|
||||
"network": a.IP,
|
||||
"wolEnabled": false,
|
||||
},
|
||||
}
|
||||
children = append(children, node)
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
func containsKeyword(name, keyword string) bool {
|
||||
if keyword == "" {
|
||||
return true
|
||||
@@ -139,7 +116,7 @@ func (api PortalApi) WebsitesTreeEndpoint(c echo.Context) error {
|
||||
continue
|
||||
}
|
||||
node := maps.Map{
|
||||
"key": w.ID,
|
||||
"key": "website_" + w.ID,
|
||||
"title": w.Name,
|
||||
"isLeaf": true,
|
||||
"extra": maps.Map{
|
||||
@@ -159,7 +136,7 @@ func (api PortalApi) AssetsGroupTreeEndpoint(c echo.Context) error {
|
||||
tree := make([]maps.Map, 0)
|
||||
for _, g := range groups {
|
||||
node := maps.Map{
|
||||
"key": g.ID,
|
||||
"key": "group_" + g.ID,
|
||||
"title": g.Name,
|
||||
"isLeaf": false,
|
||||
"children": []interface{}{},
|
||||
@@ -186,7 +163,8 @@ func (api PortalApi) CreateSessionEndpoint(c echo.Context) error {
|
||||
|
||||
account, _ := GetCurrentAccount(c)
|
||||
|
||||
s, err := service.SessionService.Create(c.RealIP(), assetId, nt.Native, account)
|
||||
clientIP := service.PropertyService.GetClientIP(c)
|
||||
s, err := service.SessionService.Create(clientIP, assetId, nt.Native, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -201,7 +179,16 @@ func (api PortalApi) CreateSessionEndpoint(c echo.Context) error {
|
||||
"id": s.ID,
|
||||
"protocol": s.Protocol,
|
||||
"assetName": assetName,
|
||||
"strategy": maps.Map{},
|
||||
"strategy": maps.Map{
|
||||
"upload": s.Upload == "1",
|
||||
"download": s.Download == "1",
|
||||
"delete": s.Delete == "1",
|
||||
"rename": s.Rename == "1",
|
||||
"edit": s.Edit == "1",
|
||||
"copy": s.Copy == "1",
|
||||
"paste": s.Paste == "1",
|
||||
"fileSystem": s.FileSystem == "1",
|
||||
},
|
||||
"url": "",
|
||||
"watermark": maps.Map{},
|
||||
"readonly": false,
|
||||
|
||||
@@ -202,7 +202,8 @@ func (api SessionApi) SessionCreateEndpoint(c echo.Context) error {
|
||||
|
||||
user, _ := GetCurrentAccount(c)
|
||||
|
||||
s, err := service.SessionService.Create(c.RealIP(), assetId, mode, user)
|
||||
clientIP := service.PropertyService.GetClientIP(c)
|
||||
s, err := service.SessionService.Create(clientIP, assetId, mode, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+150
-10
@@ -2,7 +2,11 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"next-terminal/server/common/maps"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@@ -10,18 +14,37 @@ import (
|
||||
type SessionCommandApi struct{}
|
||||
|
||||
func (api SessionCommandApi) AllEndpoint(c echo.Context) error {
|
||||
return Success(c, []interface{}{})
|
||||
sessionId := c.QueryParam("sessionId")
|
||||
items, err := repository.SessionCommandRepository.FindBySessionId(context.TODO(), sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, items)
|
||||
}
|
||||
|
||||
func (api SessionCommandApi) PagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
sessionId := c.QueryParam("sessionId")
|
||||
|
||||
items, total, err := repository.SessionCommandRepository.FindByPage(context.TODO(), pageIndex, pageSize, sessionId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, maps.Map{
|
||||
"total": 0,
|
||||
"items": []interface{}{},
|
||||
"total": total,
|
||||
"items": items,
|
||||
})
|
||||
}
|
||||
|
||||
func (api SessionCommandApi) GetEndpoint(c echo.Context) error {
|
||||
return Success(c, nil)
|
||||
id := c.Param("id")
|
||||
var item model.SessionCommand
|
||||
err := repository.SessionCommandRepository.GetDB(context.TODO()).Where("id = ?", id).First(&item).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
type OperationLogApi struct{}
|
||||
@@ -31,17 +54,55 @@ func (api OperationLogApi) AllEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
func (api OperationLogApi) PagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
accountName := c.QueryParam("accountName")
|
||||
action := c.QueryParam("action")
|
||||
status := c.QueryParam("status")
|
||||
order := c.QueryParam("order")
|
||||
field := c.QueryParam("field")
|
||||
|
||||
items, total, err := repository.OperationLogRepository.FindByPage(context.TODO(), pageIndex, pageSize, accountName, action, status, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := make([]maps.Map, 0)
|
||||
for _, item := range items {
|
||||
result = append(result, maps.Map{
|
||||
"id": item.ID,
|
||||
"accountId": item.AccountId,
|
||||
"accountName": item.AccountName,
|
||||
"action": item.Action,
|
||||
"content": item.Content,
|
||||
"ip": item.IP,
|
||||
"region": item.Region,
|
||||
"userAgent": item.UserAgent,
|
||||
"status": item.Status,
|
||||
"errorMessage": item.ErrorMessage,
|
||||
"remark": item.Remark,
|
||||
"createdAt": item.Created,
|
||||
})
|
||||
}
|
||||
|
||||
return Success(c, maps.Map{
|
||||
"total": 0,
|
||||
"items": []interface{}{},
|
||||
"total": total,
|
||||
"items": result,
|
||||
})
|
||||
}
|
||||
|
||||
func (api OperationLogApi) ClearEndpoint(c echo.Context) error {
|
||||
if err := repository.OperationLogRepository.DeleteAll(context.TODO()); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func (api OperationLogApi) DeleteEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
if err := repository.OperationLogRepository.DeleteById(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -111,17 +172,57 @@ func (api FilesystemLogApi) AllEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
func (api FilesystemLogApi) PagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
action := c.QueryParam("action")
|
||||
|
||||
items, total, err := repository.FilesystemLogRepository.FindByPage(context.TODO(), pageIndex, pageSize, action)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := make([]maps.Map, 0)
|
||||
for _, item := range items {
|
||||
var assetName, userName string
|
||||
if item.AssetId != "" {
|
||||
asset, _ := repository.AssetRepository.FindById(context.TODO(), item.AssetId)
|
||||
assetName = asset.Name
|
||||
}
|
||||
if item.UserId != "" {
|
||||
user, _ := repository.UserRepository.FindById(context.TODO(), item.UserId)
|
||||
userName = user.Username
|
||||
}
|
||||
result = append(result, maps.Map{
|
||||
"id": item.ID,
|
||||
"assetId": item.AssetId,
|
||||
"sessionId": item.SessionId,
|
||||
"userId": item.UserId,
|
||||
"action": item.Action,
|
||||
"fileName": item.FileName,
|
||||
"createdAt": item.Created,
|
||||
"assetName": assetName,
|
||||
"userName": userName,
|
||||
})
|
||||
}
|
||||
|
||||
return Success(c, maps.Map{
|
||||
"total": 0,
|
||||
"items": []interface{}{},
|
||||
"total": total,
|
||||
"items": result,
|
||||
})
|
||||
}
|
||||
|
||||
func (api FilesystemLogApi) DeleteEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
if err := repository.FilesystemLogRepository.DeleteById(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func (api FilesystemLogApi) ClearEndpoint(c echo.Context) error {
|
||||
if err := repository.FilesystemLogRepository.DeleteAll(context.TODO()); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -161,17 +262,56 @@ func (api AccessLogApi) AllEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
func (api AccessLogApi) PagingEndpoint(c echo.Context) error {
|
||||
pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex"))
|
||||
pageSize, _ := strconv.Atoi(c.QueryParam("pageSize"))
|
||||
domain := c.QueryParam("domain")
|
||||
websiteId := c.QueryParam("websiteId")
|
||||
accountId := c.QueryParam("accountId")
|
||||
|
||||
items, total, err := repository.AccessLogRepository.FindByPage(context.TODO(), pageIndex, pageSize, domain, websiteId, accountId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := make([]maps.Map, 0)
|
||||
for _, item := range items {
|
||||
result = append(result, maps.Map{
|
||||
"id": item.ID,
|
||||
"domain": item.Domain,
|
||||
"websiteId": item.WebsiteId,
|
||||
"accountId": item.AccountId,
|
||||
"method": item.Method,
|
||||
"uri": item.Uri,
|
||||
"statusCode": item.StatusCode,
|
||||
"responseSize": item.ResponseSize,
|
||||
"clientIp": item.ClientIp,
|
||||
"region": item.Region,
|
||||
"userAgent": item.UserAgent,
|
||||
"referer": item.Referer,
|
||||
"requestTime": item.RequestTime,
|
||||
"responseTime": item.ResponseTime,
|
||||
"createdAt": item.Created,
|
||||
})
|
||||
}
|
||||
|
||||
return Success(c, maps.Map{
|
||||
"total": 0,
|
||||
"items": []interface{}{},
|
||||
"total": total,
|
||||
"items": result,
|
||||
})
|
||||
}
|
||||
|
||||
func (api AccessLogApi) DeleteEndpoint(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
if err := repository.AccessLogRepository.DeleteById(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
func (api AccessLogApi) ClearEndpoint(c echo.Context) error {
|
||||
if err := repository.AccessLogRepository.DeleteAll(context.TODO()); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -537,13 +537,59 @@ func (api DbWorkOrderApi) DeleteEndpoint(c echo.Context) error {
|
||||
type DatabaseSQLLogApi struct{}
|
||||
|
||||
func (api DatabaseSQLLogApi) 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")
|
||||
status := c.QueryParam("status")
|
||||
source := c.QueryParam("source")
|
||||
order := c.QueryParam("order")
|
||||
field := c.QueryParam("field")
|
||||
|
||||
items, total, err := repository.DatabaseSQLLogRepository.FindByPage(context.TODO(), pageIndex, pageSize, assetId, userId, status, source, order, field)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := make([]maps.Map, 0)
|
||||
for _, item := range items {
|
||||
var assetName, userName string
|
||||
if item.AssetId != "" {
|
||||
asset, _ := repository.DatabaseAssetRepository.FindById(context.TODO(), item.AssetId)
|
||||
assetName = asset.Name
|
||||
}
|
||||
if item.UserId != "" {
|
||||
user, _ := repository.UserRepository.FindById(context.TODO(), item.UserId)
|
||||
userName = user.Username
|
||||
}
|
||||
result = append(result, maps.Map{
|
||||
"id": item.ID,
|
||||
"assetId": item.AssetId,
|
||||
"assetName": assetName,
|
||||
"database": item.Database,
|
||||
"userId": item.UserId,
|
||||
"userName": userName,
|
||||
"clientIp": item.ClientIP,
|
||||
"sql": item.SQL,
|
||||
"durationMs": item.DurationMs,
|
||||
"rowsAffected": item.RowsAffected,
|
||||
"status": item.Status,
|
||||
"errorMessage": item.ErrorMessage,
|
||||
"source": item.Source,
|
||||
"createdAt": item.Created,
|
||||
})
|
||||
}
|
||||
|
||||
return Success(c, maps.Map{
|
||||
"total": 0,
|
||||
"items": []interface{}{},
|
||||
"total": total,
|
||||
"items": result,
|
||||
})
|
||||
}
|
||||
|
||||
func (api DatabaseSQLLogApi) ClearEndpoint(c echo.Context) error {
|
||||
if err := repository.DatabaseSQLLogRepository.DeleteAll(context.TODO()); err != nil {
|
||||
return err
|
||||
}
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -188,7 +188,7 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error {
|
||||
if err != nil {
|
||||
service.SessionService.CloseSessionById(sessionId, TunnelClosed, "远程连接已关闭")
|
||||
} else {
|
||||
_ = termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, ""))
|
||||
_ = termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, msg.Content))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -358,7 +358,7 @@ func (api WebTerminalApi) AccessTerminalEndpoint(c echo.Context) error {
|
||||
termHandler.Write([]byte(msg.Content))
|
||||
case Ping:
|
||||
termHandler.SendRequest()
|
||||
termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, ""))
|
||||
termHandler.SendMessageToWebSocket(dto.NewMessage(Ping, msg.Content))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
+117
-3
@@ -3,18 +3,27 @@ package api
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"next-terminal/server/common"
|
||||
"next-terminal/server/common/term"
|
||||
"next-terminal/server/dto"
|
||||
"next-terminal/server/global/session"
|
||||
"next-terminal/server/model"
|
||||
"next-terminal/server/repository"
|
||||
"next-terminal/server/utils"
|
||||
)
|
||||
|
||||
const maxOutputLength = 10000
|
||||
|
||||
type TermHandler struct {
|
||||
sessionId string
|
||||
userId string
|
||||
assetId string
|
||||
isRecording bool
|
||||
webSocket *websocket.Conn
|
||||
nextTerminal *term.NextTerminal
|
||||
@@ -24,6 +33,10 @@ type TermHandler struct {
|
||||
tick *time.Ticker
|
||||
mutex sync.Mutex
|
||||
buf bytes.Buffer
|
||||
commandBuf bytes.Buffer
|
||||
outputBuf bytes.Buffer
|
||||
lastCommand *model.SessionCommand
|
||||
outputMutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewTermHandler(userId, assetId, sessionId string, isRecording bool, ws *websocket.Conn, nextTerminal *term.NextTerminal) *TermHandler {
|
||||
@@ -32,6 +45,8 @@ func NewTermHandler(userId, assetId, sessionId string, isRecording bool, ws *web
|
||||
|
||||
return &TermHandler{
|
||||
sessionId: sessionId,
|
||||
userId: userId,
|
||||
assetId: assetId,
|
||||
isRecording: isRecording,
|
||||
webSocket: ws,
|
||||
nextTerminal: nextTerminal,
|
||||
@@ -48,9 +63,9 @@ func (r *TermHandler) Start() {
|
||||
}
|
||||
|
||||
func (r *TermHandler) Stop() {
|
||||
// 会话结束时记录最后一个命令
|
||||
r.tick.Stop()
|
||||
r.cancel()
|
||||
r.saveLastCommandOutput()
|
||||
}
|
||||
|
||||
func (r *TermHandler) readFormTunnel() {
|
||||
@@ -86,8 +101,8 @@ func (r *TermHandler) writeToWebsocket() {
|
||||
if r.isRecording && r.nextTerminal.Recorder != nil {
|
||||
_ = r.nextTerminal.Recorder.WriteData(s)
|
||||
}
|
||||
// 监控
|
||||
SendObData(r.sessionId, s)
|
||||
r.collectOutput(s)
|
||||
r.buf.Reset()
|
||||
case data := <-r.dataChan:
|
||||
if data != utf8.RuneError {
|
||||
@@ -101,12 +116,111 @@ func (r *TermHandler) writeToWebsocket() {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TermHandler) collectOutput(output string) {
|
||||
r.outputMutex.Lock()
|
||||
defer r.outputMutex.Unlock()
|
||||
if r.outputBuf.Len()+len(output) <= maxOutputLength {
|
||||
r.outputBuf.WriteString(output)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TermHandler) getAndClearOutput() string {
|
||||
r.outputMutex.Lock()
|
||||
defer r.outputMutex.Unlock()
|
||||
output := r.outputBuf.String()
|
||||
r.outputBuf.Reset()
|
||||
return output
|
||||
}
|
||||
|
||||
func (r *TermHandler) saveLastCommandOutput() {
|
||||
r.outputMutex.Lock()
|
||||
defer r.outputMutex.Unlock()
|
||||
if r.lastCommand != nil && r.outputBuf.Len() > 0 {
|
||||
output := cleanOutput(r.outputBuf.String())
|
||||
if len(output) > maxOutputLength {
|
||||
output = output[:maxOutputLength]
|
||||
}
|
||||
r.lastCommand.Output = output
|
||||
_ = repository.SessionCommandRepository.Create(context.TODO(), r.lastCommand)
|
||||
r.lastCommand = nil
|
||||
r.outputBuf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func cleanOutput(output string) string {
|
||||
output = strings.ReplaceAll(output, "\x1b[0m", "")
|
||||
output = strings.ReplaceAll(output, "\x1b[?2004h", "")
|
||||
output = strings.ReplaceAll(output, "\x1b[?2004l", "")
|
||||
var result strings.Builder
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
result.WriteString(line)
|
||||
result.WriteString("\n")
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(result.String())
|
||||
}
|
||||
|
||||
func (r *TermHandler) Write(input []byte) error {
|
||||
// 正常的字符输入
|
||||
for _, b := range input {
|
||||
switch b {
|
||||
case 13: // Enter key
|
||||
command := strings.TrimSpace(r.commandBuf.String())
|
||||
if command != "" {
|
||||
r.saveLastCommandOutput()
|
||||
r.lastCommand = &model.SessionCommand{
|
||||
ID: utils.UUID(),
|
||||
SessionId: r.sessionId,
|
||||
RiskLevel: detectRiskLevel(command),
|
||||
Command: command,
|
||||
Output: "",
|
||||
Created: common.NowJsonTime(),
|
||||
}
|
||||
}
|
||||
r.commandBuf.Reset()
|
||||
case 127, 8: // Backspace, Delete
|
||||
if r.commandBuf.Len() > 0 {
|
||||
r.commandBuf.Truncate(r.commandBuf.Len() - 1)
|
||||
}
|
||||
case 27: // Escape sequence (arrow keys, etc.)
|
||||
default:
|
||||
if b >= 32 && b < 127 { // Printable ASCII
|
||||
r.commandBuf.WriteByte(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err := r.nextTerminal.Write(input)
|
||||
return err
|
||||
}
|
||||
|
||||
func detectRiskLevel(command string) int {
|
||||
highRiskPatterns := []string{
|
||||
"rm -rf /", "rm -rf /*", "mkfs", "dd if=", "> /dev/sd",
|
||||
"chmod -R 777", "chown -R", ":(){ :|:& };:",
|
||||
}
|
||||
mediumRiskPatterns := []string{
|
||||
"rm -rf", "rm -r", "passwd", "useradd", "userdel",
|
||||
"chmod 777", "chmod -R", "chown", "wget", "curl",
|
||||
"apt-get", "yum install", "dnf install", "pacman",
|
||||
"systemctl stop", "systemctl disable", "service stop",
|
||||
"iptables -F", "ufw disable", "setenforce 0",
|
||||
}
|
||||
|
||||
lowerCmd := strings.ToLower(command)
|
||||
for _, pattern := range highRiskPatterns {
|
||||
if strings.Contains(lowerCmd, strings.ToLower(pattern)) {
|
||||
return 2
|
||||
}
|
||||
}
|
||||
for _, pattern := range mediumRiskPatterns {
|
||||
if strings.Contains(lowerCmd, strings.ToLower(pattern)) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *TermHandler) WindowChange(h int, w int) error {
|
||||
return r.nextTerminal.WindowChange(h, w)
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@ func (userApi UserApi) CreateEndpoint(c echo.Context) (err error) {
|
||||
}
|
||||
|
||||
if err := service.UserService.CreateUser(item); err != nil {
|
||||
recordOperationLog(c, "user-add", "创建用户: "+item.Username, "failed", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
recordOperationLog(c, "user-add", "创建用户: "+item.Username, "success", "")
|
||||
return Success(c, item)
|
||||
}
|
||||
|
||||
@@ -68,9 +70,11 @@ func (userApi UserApi) UpdateEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
if err := service.UserService.UpdateUser(id, item); err != nil {
|
||||
recordOperationLog(c, "user-edit", "更新用户: "+item.Username, "failed", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
recordOperationLog(c, "user-edit", "更新用户: "+item.Username, "success", "")
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -83,9 +87,11 @@ func (userApi UserApi) UpdateStatusEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
if err := service.UserService.UpdateStatusById(id, status); err != nil {
|
||||
recordOperationLog(c, "user-edit", "更新用户状态", "failed", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
recordOperationLog(c, "user-edit", "更新用户状态", "success", "")
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
@@ -102,10 +108,12 @@ func (userApi UserApi) DeleteEndpoint(c echo.Context) error {
|
||||
return Fail(c, -1, "不允许删除自身账户")
|
||||
}
|
||||
if err := service.UserService.DeleteUserById(userId); err != nil {
|
||||
recordOperationLog(c, "user-del", "删除用户: "+ids, "failed", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
recordOperationLog(c, "user-del", "删除用户: "+ids, "success", "")
|
||||
return Success(c, nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ func buildWebsiteGroupTree(groups []model.WebsiteGroup, parentId string) []maps.
|
||||
"id": g.ID,
|
||||
"name": g.Name,
|
||||
"title": g.Name,
|
||||
"key": g.ID,
|
||||
"key": "wgroup_" + g.ID,
|
||||
"value": g.ID,
|
||||
}
|
||||
children := buildWebsiteGroupTree(groups, g.ID)
|
||||
|
||||
Reference in New Issue
Block a user