1. 菜单路由修复: /roles /users /logs-audit /terminal-audit 等系统管理子菜单 通过 useLocation 同步 URL 路径到 Tab 选中状态 2. WebSSH错误提示优化: Go error → 友好中文提示 i/o timeout → 连接超时(防火墙或网络不通) connection refused → 端口未开放/SSH服务未启动 cannot find session key → 密码或密钥认证失败 连接提示增加端口号显示 3. 角色权限展示: 31项权限种子数据 (资产管理/云资源/K8s/发布/监控/AIOps/系统管理) 4个角色权限分配: admin(31) ops(27) developer(16) viewer(3) 前端角色卡片显示权限名称而非原始code 4. 操作审计修复: 删除/创建/更新服务器均记录审计日志 批量删除逐条记录审计 5. WebSSH终端审计 (JumpServer模式): - 新增 TerminalSession 模型记录会话(用户/服务器/时间/状态) - webshell.go 录制终端输出(1MB上限) - 终端审计页面: 会话列表+状态筛选+批量删除 - 操作回放: xterm.js 重放录制的终端输出 - WS连接增加JWT鉴权+token传递
148 lines
4.4 KiB
Go
148 lines
4.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"ezops/internal/models"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// TerminalSessionHandler 终端会话审计处理器 // Logan.Li
|
|
type TerminalSessionHandler struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewTerminalSessionHandler 创建终端会话审计处理器
|
|
func NewTerminalSessionHandler(db *gorm.DB) *TerminalSessionHandler {
|
|
return &TerminalSessionHandler{db: db}
|
|
}
|
|
|
|
// ListTerminalSessions 终端会话列表
|
|
// GET /api/v1/terminal-sessions?server_id=&username=&status=&page=&page_size=
|
|
func (h *TerminalSessionHandler) ListTerminalSessions(c *gin.Context) {
|
|
var req models.TerminalSessionQuery
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()})
|
|
return
|
|
}
|
|
if req.Page <= 0 {
|
|
req.Page = 1
|
|
}
|
|
if req.PageSize <= 0 {
|
|
req.PageSize = 20
|
|
}
|
|
|
|
query := h.db.Model(&models.TerminalSession{})
|
|
if req.ServerID > 0 {
|
|
query = query.Where("server_id = ?", req.ServerID)
|
|
}
|
|
if req.Username != "" {
|
|
query = query.Where("username LIKE ?", "%"+req.Username+"%")
|
|
}
|
|
if req.Status != "" {
|
|
query = query.Where("status = ?", req.Status)
|
|
}
|
|
|
|
var total int64
|
|
if err := query.Count(&total).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "查询失败"})
|
|
return
|
|
}
|
|
|
|
var sessions []models.TerminalSession
|
|
if err := query.Order("id DESC").
|
|
Offset((req.Page - 1) * req.PageSize).
|
|
Limit(req.PageSize).
|
|
Find(&sessions).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "查询失败"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, models.Response{Code: 200, Data: models.ListResponse{
|
|
List: sessions,
|
|
Total: total,
|
|
}})
|
|
}
|
|
|
|
// GetTerminalSession 终端会话详情
|
|
// GET /api/v1/terminal-sessions/:id
|
|
func (h *TerminalSessionHandler) GetTerminalSession(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "无效的会话ID"})
|
|
return
|
|
}
|
|
|
|
var session models.TerminalSession
|
|
if err := h.db.First(&session, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "会话不存在"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "查询失败"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, models.Response{Code: 200, Data: session})
|
|
}
|
|
|
|
// GetTerminalRecord 终端会话录制数据(用于回放)
|
|
// GET /api/v1/terminal-sessions/:id/record
|
|
func (h *TerminalSessionHandler) GetTerminalRecord(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "无效的会话ID"})
|
|
return
|
|
}
|
|
|
|
var session models.TerminalSession
|
|
if err := h.db.First(&session, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "会话不存在"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "查询失败"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, models.Response{Code: 200, Data: session.RecordData})
|
|
}
|
|
|
|
// DeleteTerminalSession 删除终端会话记录
|
|
// DELETE /api/v1/terminal-sessions/:id
|
|
func (h *TerminalSessionHandler) DeleteTerminalSession(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "无效的会话ID"})
|
|
return
|
|
}
|
|
|
|
if err := h.db.Delete(&models.TerminalSession{}, id).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"})
|
|
}
|
|
|
|
// BatchDeleteTerminalSessions 批量删除终端会话记录
|
|
// POST /api/v1/terminal-sessions/batch-delete
|
|
func (h *TerminalSessionHandler) BatchDeleteTerminalSessions(c *gin.Context) {
|
|
var req models.IDsRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "无效的请求参数"})
|
|
return
|
|
}
|
|
|
|
result := h.db.Where("id IN ?", req.IDs).Delete(&models.TerminalSession{})
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功", Data: gin.H{"deleted": result.RowsAffected}})
|
|
}
|