Files
rose_cat707 0d84b07f68
CI / docker (push) Successful in 1m58s
feat: Wormhole 内网穿透与反向代理网关
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。
Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 17:52:43 +08:00

175 lines
5.0 KiB
Go

package api
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/wormhole/wormhole/internal/auth"
"github.com/wormhole/wormhole/internal/store"
)
func (s *Server) dashboardRankings(c *gin.Context) {
rankType := c.DefaultQuery("type", "tunnels")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "50"))
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 50
}
if pageSize > 500 {
pageSize = 500
}
offset := (page - 1) * pageSize
q := c.Query("q")
switch rankType {
case "tunnels":
items, total, err := s.store.ListTunnelsByFlow(pageSize, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
case "hosts":
items, total, err := s.store.ListHostsByFlow(pageSize, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
case "ips":
items, total, err := s.store.ListAllRequestIPs(pageSize, offset, q)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"items": requestIPViews(s.store, items), "total": total})
default:
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid type"})
}
}
func (s *Server) listAPIKeys(c *gin.Context) {
claims := getClaims(c)
keys, err := s.store.ListAPIKeys(claims.UserID, auth.IsAdmin(claims.Role))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, keys)
}
func (s *Server) createAPIKey(c *gin.Context) {
claims := getClaims(c)
var req struct {
Name string `json:"name"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
return
}
key, plain, err := s.store.CreateAPIKey(req.Name, claims.UserID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"key": key,
"api_key": plain,
"note": "请妥善保存 API Key,仅显示一次",
})
}
func (s *Server) deleteAPIKey(c *gin.Context) {
claims := getClaims(c)
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if err := s.store.DeleteAPIKey(uint(id), claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
func (s *Server) pauseTunnel(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
claims := getClaims(c)
existing, err := s.store.GetTunnelByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
if err := store.ClientOwnerCheck(s.store, existing.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
return
}
_ = s.proxy.StopTunnel(uint(id))
existing.Status = false
existing.RunStatus = false
_ = s.store.UpdateTunnel(existing)
s.cache.UpsertTunnel(existing)
c.JSON(http.StatusOK, gin.H{"ok": true})
}
func (s *Server) resumeTunnel(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
claims := getClaims(c)
existing, err := s.store.GetTunnelByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
if err := store.ClientOwnerCheck(s.store, existing.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
return
}
existing.Status = true
_ = s.store.UpdateTunnel(existing)
s.cache.UpsertTunnel(existing)
_ = s.proxy.StartTunnel(uint(id))
c.JSON(http.StatusOK, gin.H{"ok": true})
}
func (s *Server) pauseHost(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
claims := getClaims(c)
existing, err := s.store.GetHostByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
if err := store.ClientOwnerCheck(s.store, existing.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
return
}
existing.Status = false
_ = s.store.UpdateHost(existing)
s.cache.UpsertHost(existing)
c.JSON(http.StatusOK, gin.H{"ok": true})
}
func (s *Server) resumeHost(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
claims := getClaims(c)
existing, err := s.store.GetHostByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
if err := store.ClientOwnerCheck(s.store, existing.ClientID, claims.UserID, auth.IsAdmin(claims.Role)); err != nil {
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
return
}
existing.Status = true
_ = s.store.UpdateHost(existing)
s.cache.UpsertHost(existing)
c.JSON(http.StatusOK, gin.H{"ok": true})
}
func (s *Server) openapiDoc(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.String(http.StatusOK, openAPISpec)
}