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,174 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user