Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。 Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/wormhole/wormhole/internal/store"
|
||||
)
|
||||
|
||||
type hostRequest struct {
|
||||
store.Host
|
||||
VisitorIDs []uint `json:"visitor_ids"`
|
||||
}
|
||||
|
||||
type tunnelRequest struct {
|
||||
store.Tunnel
|
||||
VisitorIDs []uint `json:"visitor_ids"`
|
||||
}
|
||||
|
||||
type hostView struct {
|
||||
store.Host
|
||||
VisitorIDs []uint `json:"visitor_ids"`
|
||||
}
|
||||
|
||||
type tunnelView struct {
|
||||
store.Tunnel
|
||||
VisitorIDs []uint `json:"visitor_ids"`
|
||||
}
|
||||
|
||||
func (s *Server) enrichHostView(h store.Host) (hostView, error) {
|
||||
ids, err := s.store.ListResourceVisitorIDs("host", h.ID)
|
||||
if err != nil {
|
||||
return hostView{}, err
|
||||
}
|
||||
return hostView{Host: h, VisitorIDs: ids}, nil
|
||||
}
|
||||
|
||||
func (s *Server) enrichTunnelView(t store.Tunnel) (tunnelView, error) {
|
||||
ids, err := s.store.ListResourceVisitorIDs("tunnel", t.ID)
|
||||
if err != nil {
|
||||
return tunnelView{}, err
|
||||
}
|
||||
return tunnelView{Tunnel: t, VisitorIDs: ids}, nil
|
||||
}
|
||||
|
||||
func (s *Server) saveResourceVisitors(resourceType string, resourceID uint, enabled bool, bindMode string, visitorIDs []uint) error {
|
||||
if !enabled {
|
||||
_ = s.store.SetResourceVisitors(resourceType, resourceID, nil)
|
||||
s.cache.SetResourceVisitors(resourceType, resourceID, nil)
|
||||
return nil
|
||||
}
|
||||
if bindMode == "" {
|
||||
bindMode = "all"
|
||||
}
|
||||
if bindMode == "selected" {
|
||||
if len(visitorIDs) == 0 {
|
||||
return fmt.Errorf("请至少选择一个访客用户")
|
||||
}
|
||||
if err := s.validateVisitorIDs(visitorIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.store.SetResourceVisitors(resourceType, resourceID, visitorIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
s.cache.SetResourceVisitors(resourceType, resourceID, visitorIDs)
|
||||
return nil
|
||||
}
|
||||
_ = s.store.SetResourceVisitors(resourceType, resourceID, nil)
|
||||
s.cache.SetResourceVisitors(resourceType, resourceID, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) validateVisitorIDs(ids []uint) error {
|
||||
for _, id := range ids {
|
||||
user, err := s.store.GetUserByID(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("访客用户 #%d 不存在", id)
|
||||
}
|
||||
if user.Role != "visitor" {
|
||||
return fmt.Errorf("用户 %s 不是访客账号", user.Username)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeVisitorSaveError(c *gin.Context, err error) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
}
|
||||
|
||||
type requestIPView struct {
|
||||
ID uint `json:"id"`
|
||||
ResourceType string `json:"resource_type"`
|
||||
ResourceID uint `json:"resource_id"`
|
||||
ResourceLabel string `json:"resource_label"`
|
||||
IP string `json:"ip"`
|
||||
HitCount int64 `json:"hit_count"`
|
||||
LastSeenAt time.Time `json:"last_seen_at"`
|
||||
}
|
||||
|
||||
func requestIPViews(st *store.Store, items []store.RequestIP) []requestIPView {
|
||||
out := make([]requestIPView, 0, len(items))
|
||||
for _, item := range items {
|
||||
label := visitorResourceLabel(st, item.ResourceType, item.ResourceID)
|
||||
if label == "" && item.ResourceID > 0 {
|
||||
label = fmt.Sprintf("#%d", item.ResourceID)
|
||||
}
|
||||
out = append(out, requestIPView{
|
||||
ID: item.ID,
|
||||
ResourceType: item.ResourceType,
|
||||
ResourceID: item.ResourceID,
|
||||
ResourceLabel: label,
|
||||
IP: item.IP,
|
||||
HitCount: item.HitCount,
|
||||
LastSeenAt: item.LastSeenAt,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func visitorResourceLabel(st *store.Store, resourceType string, resourceID uint) string {
|
||||
switch resourceType {
|
||||
case "host":
|
||||
h, err := st.GetHostByID(resourceID)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
label := h.Host
|
||||
if h.Location != "" && h.Location != "/" {
|
||||
label += h.Location
|
||||
}
|
||||
return label
|
||||
case "tunnel":
|
||||
t, err := st.GetTunnelByID(resourceID)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if t.Name != "" {
|
||||
return t.Name + " :" + strconv.Itoa(t.ListenPort)
|
||||
}
|
||||
return ":" + strconv.Itoa(t.ListenPort)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user