mirror of
https://github.com/veops/oneterm.git
synced 2025-09-27 11:42:08 +08:00
271 lines
7.6 KiB
Go
271 lines
7.6 KiB
Go
package controller
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/samber/lo"
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm/clause"
|
|
|
|
"github.com/veops/oneterm/pkg/logger"
|
|
"github.com/veops/oneterm/pkg/server/auth/acl"
|
|
"github.com/veops/oneterm/pkg/server/model"
|
|
"github.com/veops/oneterm/pkg/server/storage/db/mysql"
|
|
)
|
|
|
|
var (
|
|
onlineSession = &sync.Map{}
|
|
|
|
sessionPostHooks = []postHook[*model.Session]{
|
|
func(ctx *gin.Context, data []*model.Session) {
|
|
sessionIds := lo.Map(data, func(d *model.Session, _ int) string { return d.SessionId })
|
|
if len(sessionIds) <= 0 {
|
|
return
|
|
}
|
|
post := make([]*model.CmdCount, 0)
|
|
if err := mysql.DB.
|
|
Model(&model.SessionCmd{}).
|
|
Select("session_id, COUNT(*) AS count").
|
|
Where("session_id IN ?", sessionIds).
|
|
Group("session_id").
|
|
Find(&post).
|
|
Error; err != nil {
|
|
logger.L.Error("gateway posthookfailed", zap.Error(err))
|
|
return
|
|
}
|
|
m := lo.SliceToMap(post, func(p *model.CmdCount) (string, int64) { return p.SessionId, p.Count })
|
|
for _, d := range data {
|
|
d.CmdCount = m[d.SessionId]
|
|
}
|
|
},
|
|
func(ctx *gin.Context, data []*model.Session) {
|
|
now := time.Now()
|
|
for _, d := range data {
|
|
t := now
|
|
if d.ClosedAt != nil {
|
|
t = *d.ClosedAt
|
|
}
|
|
d.Duration = int64(t.Sub(d.CreatedAt).Seconds())
|
|
}
|
|
},
|
|
}
|
|
)
|
|
|
|
func Init() (err error) {
|
|
sessions := make([]*model.Session, 0)
|
|
err = mysql.DB.
|
|
Model(&model.Session{}).
|
|
Where("status = ?", model.SESSIONSTATUS_ONLINE).
|
|
Find(&sessions).
|
|
Error
|
|
if err != nil {
|
|
return
|
|
}
|
|
ctx := &gin.Context{}
|
|
for _, s := range sessions {
|
|
if s.SessionType == model.SESSIONTYPE_WEB {
|
|
doOfflineOnlineSession(ctx, s.SessionId, "")
|
|
continue
|
|
}
|
|
s.Monitors = &sync.Map{}
|
|
onlineSession.LoadOrStore(s.SessionId, s)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// UpsertSession godoc
|
|
//
|
|
// @Tags session
|
|
// @Param sessino body model.Session true "session"
|
|
// @Success 200 {object} HttpResponse
|
|
// @Router /session [post]
|
|
func (c *Controller) UpsertSession(ctx *gin.Context) {
|
|
data := &model.Session{}
|
|
if err := ctx.BindJSON(data); err != nil {
|
|
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrInvalidArgument, Data: map[string]any{"err": err}})
|
|
return
|
|
}
|
|
if err := mysql.DB.
|
|
Clauses(clause.OnConflict{
|
|
DoUpdates: clause.AssignmentColumns([]string{"status", "closed_at"}),
|
|
}).
|
|
Create(data).
|
|
Error; err != nil {
|
|
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
|
|
return
|
|
}
|
|
|
|
switch data.Status {
|
|
case model.SESSIONSTATUS_ONLINE:
|
|
if data.Monitors == nil {
|
|
data.Monitors = &sync.Map{}
|
|
}
|
|
_, ok := onlineSession.LoadOrStore(data.SessionId, data)
|
|
if ok {
|
|
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": "failed to loadstore online session"}})
|
|
return
|
|
}
|
|
case model.SESSIONSTATUS_OFFLINE:
|
|
// doOfflineOnlineSession(ctx, data.SessionId, "")
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, defaultHttpResponse)
|
|
}
|
|
|
|
// CreateSessionCommand godoc
|
|
//
|
|
// @Tags session
|
|
// @Param sessioncmd body model.SessionCmd true "SessionCmd"
|
|
// @Success 200 {object} HttpResponse
|
|
// @Router /session/cmd [post]
|
|
func (c *Controller) CreateSessionCmd(ctx *gin.Context) {
|
|
data := &model.SessionCmd{}
|
|
if err := ctx.BindJSON(data); err != nil {
|
|
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrInvalidArgument, Data: map[string]any{"err": err}})
|
|
return
|
|
}
|
|
if err := mysql.DB.
|
|
Create(data).
|
|
Error; err != nil {
|
|
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, defaultHttpResponse)
|
|
}
|
|
|
|
// GetSessions godoc
|
|
//
|
|
// @Tags session
|
|
// @Param page_index query int true "page_index"
|
|
// @Param page_size query int true "page_size"
|
|
// @Param search query string false "search"
|
|
// @Param status query int false "status, online=1, offline=2"
|
|
// @Param start query string false "start, RFC3339"
|
|
// @Param end query string false "end, RFC3339"
|
|
// @Param uid query int false "uid"
|
|
// @Param asset_id query int false "asset id"
|
|
// @Param client_ip query string false "client_ip"
|
|
// @Success 200 {object} HttpResponse{data=ListData{list=[]model.Session}}
|
|
// @Router /session [get]
|
|
func (c *Controller) GetSessions(ctx *gin.Context) {
|
|
db := mysql.DB.Model(&model.Session{})
|
|
currentUser, _ := acl.GetSessionFromCtx(ctx)
|
|
if !acl.IsAdmin(currentUser) {
|
|
db = db.Where("uid = ?", currentUser.Uid)
|
|
}
|
|
db = filterSearch(ctx, db, "user_name", "asset_info", "gateway_info", "account_info")
|
|
db, err := filterStartEnd(ctx, db)
|
|
if err != nil {
|
|
return
|
|
}
|
|
db = filterEqual(ctx, db, "status", "uid", "asset_id", "client_ip")
|
|
|
|
doGet[*model.Session](ctx, false, db, "", sessionPostHooks...)
|
|
}
|
|
|
|
// GetSessionCmds godoc
|
|
//
|
|
// @Tags session
|
|
// @Param page_index query int true "page_index"
|
|
// @Param page_size query int true "page_size"
|
|
// @Param session_id path string true "session id"
|
|
// @Param search query string true "search"
|
|
// @Success 200 {object} HttpResponse{data=ListData{list=[]model.SessionCmd}}
|
|
// @Router /session/:session_id/cmd [get]
|
|
func (c *Controller) GetSessionCmds(ctx *gin.Context) {
|
|
db := mysql.DB.Model(&model.SessionCmd{})
|
|
db = db.Where("session_id = ?", ctx.Param("session_id"))
|
|
db = filterSearch(ctx, db, "cmd", "result")
|
|
|
|
doGet[*model.SessionCmd](ctx, false, db, "")
|
|
}
|
|
|
|
// GetSessionOptionAsset godoc
|
|
//
|
|
// @Tags session
|
|
// @Success 200 {object} HttpResponse{data=ListData{list=[]model.SessionOptionAsset}}
|
|
// @Router /session/option/asset [get]
|
|
func (c *Controller) GetSessionOptionAsset(ctx *gin.Context) {
|
|
opts := make([]*model.SessionOptionAsset, 0)
|
|
if err := mysql.DB.
|
|
Model(&model.Asset{}).
|
|
Select("id, name").
|
|
Find(&opts).
|
|
Error; err != nil {
|
|
ctx.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, NewHttpResponseWithData(opts))
|
|
}
|
|
|
|
// GetSessionOptionClientIp godoc
|
|
//
|
|
// @Tags session
|
|
// @Success 200 {object} HttpResponse{data=[]string}
|
|
// @Router /session/option/clientip [get]
|
|
func (c *Controller) GetSessionOptionClientIp(ctx *gin.Context) {
|
|
opts := make([]string, 0)
|
|
if err := mysql.DB.
|
|
Model(&model.Session{}).
|
|
Distinct("client_ip").
|
|
Find(&opts).
|
|
Error; err != nil {
|
|
ctx.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, NewHttpResponseWithData(opts))
|
|
}
|
|
|
|
// CreateSessionReplay godoc
|
|
//
|
|
// @Tags session
|
|
// @Param session_id path string true "session id"
|
|
// @Success 200 {object} HttpResponse
|
|
// @Router /session/replay/:session_id [post]
|
|
func (c *Controller) CreateSessionReplay(ctx *gin.Context) {
|
|
file, _, err := ctx.Request.FormFile("replay.cast")
|
|
if err != nil {
|
|
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrInvalidArgument, Data: map[string]any{"err": err}})
|
|
return
|
|
}
|
|
|
|
content, err := io.ReadAll(file)
|
|
if err != nil {
|
|
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrInvalidArgument, Data: map[string]any{"err": err}})
|
|
return
|
|
}
|
|
|
|
f, err := os.Create(filepath.Join("/replay", fmt.Sprintf("%s.cast", ctx.Param("session_id"))))
|
|
if err != nil {
|
|
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
|
|
return
|
|
}
|
|
defer f.Close()
|
|
f.Write(content)
|
|
|
|
ctx.JSON(http.StatusOK, defaultHttpResponse)
|
|
}
|
|
|
|
// GetSessionReplay godoc
|
|
//
|
|
// @Tags session
|
|
// @Param session_id path string true "session id"
|
|
// @Success 200 {object} string
|
|
// @Router /session/replay/:session_id [get]
|
|
func (c *Controller) GetSessionReplay(ctx *gin.Context) {
|
|
sessionId := ctx.Param("session_id")
|
|
filename := fmt.Sprintf("%s.cast", sessionId)
|
|
ctx.FileAttachment(filepath.Join("/replay", filename), filename)
|
|
}
|