feat: 日志中心 + GRE协议支持 + 流量监控 + 部署脚本

- 新增日志中心功能 (api/logapi.go + Logs.vue)
- 新增GRE协议服务支持 (service/gre/)
- 新增流量监控组件 (TrafficChart.vue)
- 新增L2TP/IPSec测试用例
- 新增日志轮转服务 (logrotate.go)
- 新增部署脚本 (scripts/install.sh, uninstall.sh, service文件)
- 更新i18n国际化文件
- 优化后端API和前端路由
This commit is contained in:
opsx-tools committed 2026-07-25 08:50:34 +08:00
1 parent 7de1f70905
commit a14d9e62e6
25 files changed
+2285 -25

No files matched your search

+115
View File
@@ -231,6 +231,7 @@ func (s *Server) RegisterRoutes(r *gin.Engine) {
auth.GET("/tunnels", s.listTunnels)
auth.GET("/tunnels/:id", s.getTunnel)
auth.GET("/tunnels/:id/status", s.tunnelStatus)
auth.GET("/tunnels/:id/traffic/history", s.tunnelTrafficHistory)
auth.GET("/tunnels/:id/policy", s.getTrafficPolicy)
auth.GET("/ipsec/templates", s.ipsecTemplates)
auth.GET("/nodes", s.listNodes)
@@ -239,6 +240,11 @@ func (s *Server) RegisterRoutes(r *gin.Engine) {
auth.GET("/sessions/log", s.listConnectionLogs)
auth.GET("/traffic/alerts", s.listTrafficAlerts)
// 日志中心(分页、筛选、导出)
auth.GET("/audit-logs", s.listAuditPaged)
auth.GET("/connection-logs", s.listConnectionLogsPaged)
auth.GET("/logs/stats", s.logStats)
// 专线运维 operator+
ops := auth.Group("")
ops.Use(requireRole("admin", "operator"))
@@ -305,6 +311,9 @@ func (s *Server) RegisterRoutes(r *gin.Engine) {
admin.PUT("/plugins/:id", s.updatePlugin)
admin.DELETE("/plugins/:id", s.deletePlugin)
admin.GET("/audit", s.listAudit)
admin.GET("/audit-logs/export", s.exportAuditLogs)
admin.GET("/connection-logs/export", s.exportConnectionLogs)
admin.POST("/logs/cleanup", s.cleanupLogs)
}
}
@@ -1031,6 +1040,112 @@ func (s *Server) tunnelStatus(c *gin.Context) {
c.JSON(200, st)
}
// tunnelTrafficHistory 返回隧道流量时间序列数据
// granularity: minute(最近 1 小时,每分钟聚合), hour(默认,最近 24 小时,每小时聚合), day(最近 30 天,每天聚合)
// hours: 覆盖时间范围(默认 24)
func (s *Server) tunnelTrafficHistory(c *gin.Context) {
t, err := s.mustTunnel(c)
if err != nil {
return
}
hours, _ := strconv.Atoi(c.DefaultQuery("hours", "24"))
if hours < 1 {
hours = 1
}
if hours > 720 { // max 30 days
hours = 720
}
granularity := c.DefaultQuery("granularity", "hour")
if granularity != "minute" && granularity != "hour" && granularity != "day" {
granularity = "hour"
}
since := time.Now().Add(-time.Duration(hours) * time.Hour)
type row struct {
Time time.Time `json:"time"`
Rx int64 `json:"rx"`
Tx int64 `json:"tx"`
DeltaRx int64 `json:"delta_rx"`
DeltaTx int64 `json:"delta_tx"`
}
var results []row
switch granularity {
case "minute":
// Group by minute for fine granularity (last 1 hour typical)
var raw []model.TrafficSample
store.DB.Where("tunnel_id = ? AND created_at >= ?", t.ID, since).
Order("created_at asc").Find(&raw)
// Bucket by minute
buckets := map[time.Time]*row{}
for _, s := range raw {
key := s.CreatedAt.Truncate(time.Minute)
if _, ok := buckets[key]; !ok {
buckets[key] = &row{Time: key}
}
b := buckets[key]
b.Rx = s.RxBytes // last value in bucket wins
b.Tx = s.TxBytes
b.DeltaRx += s.DeltaRx
b.DeltaTx += s.DeltaTx
}
for _, v := range buckets {
results = append(results, *v)
}
case "day":
// Group by day
var raw []model.TrafficSample
store.DB.Where("tunnel_id = ? AND created_at >= ?", t.ID, since).
Order("created_at asc").Find(&raw)
buckets := map[time.Time]*row{}
for _, s := range raw {
key := time.Date(s.CreatedAt.Year(), s.CreatedAt.Month(), s.CreatedAt.Day(), 0, 0, 0, 0, s.CreatedAt.Location())
if _, ok := buckets[key]; !ok {
buckets[key] = &row{Time: key}
}
b := buckets[key]
b.Rx = s.RxBytes
b.Tx = s.TxBytes
b.DeltaRx += s.DeltaRx
b.DeltaTx += s.DeltaTx
}
for _, v := range buckets {
results = append(results, *v)
}
default: // "hour"
var raw []model.TrafficSample
store.DB.Where("tunnel_id = ? AND created_at >= ?", t.ID, since).
Order("created_at asc").Find(&raw)
buckets := map[time.Time]*row{}
for _, s := range raw {
key := s.CreatedAt.Truncate(time.Hour)
if _, ok := buckets[key]; !ok {
buckets[key] = &row{Time: key}
}
b := buckets[key]
b.Rx = s.RxBytes
b.Tx = s.TxBytes
b.DeltaRx += s.DeltaRx
b.DeltaTx += s.DeltaTx
}
for _, v := range buckets {
results = append(results, *v)
}
}
if results == nil {
results = []row{}
}
c.JSON(200, gin.H{
"tunnel_id": t.ID,
"granularity": granularity,
"hours": hours,
"samples": results,
})
}
// tunnelLogs 通过 SSE 实时推送专线实例日志。
// 读取 <data>/instances/<id>-<protocol>/instance.log,先发送尾部 100 行历史,再 tail 新增行。
func (s *Server) tunnelLogs(c *gin.Context) {