All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 1m16s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 1m4s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 2m29s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 54s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 2m35s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 55s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 2m21s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m35s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 1m1s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m55s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m39s
- Added log streaming functionality for clients, allowing real-time log access via SSE. - Introduced LogHandler to manage log streaming requests and responses. - Implemented LogSessionManager to handle active log sessions and listeners. - Enhanced protocol with log-related message types and structures. - Created Logger for client-side logging, supporting various log levels and file output. - Developed LogViewer component for the web interface to display and filter logs. - Updated API to support log stream creation and management. - Added support for querying logs by level and searching through log messages.
198 lines
5.9 KiB
Go
198 lines
5.9 KiB
Go
package router
|
||
|
||
import (
|
||
"io"
|
||
"io/fs"
|
||
"net/http"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
swaggerFiles "github.com/swaggo/files"
|
||
ginSwagger "github.com/swaggo/gin-swagger"
|
||
|
||
"github.com/gotunnel/internal/server/router/handler"
|
||
"github.com/gotunnel/internal/server/router/middleware"
|
||
"github.com/gotunnel/pkg/auth"
|
||
)
|
||
|
||
// GinRouter Gin 路由管理器
|
||
type GinRouter struct {
|
||
Engine *gin.Engine
|
||
}
|
||
|
||
// New 创建 Gin 路由管理器
|
||
func New() *GinRouter {
|
||
gin.SetMode(gin.ReleaseMode)
|
||
engine := gin.New()
|
||
return &GinRouter{Engine: engine}
|
||
}
|
||
|
||
// Handler 返回 http.Handler
|
||
func (r *GinRouter) Handler() http.Handler {
|
||
return r.Engine
|
||
}
|
||
|
||
// SetupRoutes 配置所有路由
|
||
func (r *GinRouter) SetupRoutes(app handler.AppInterface, jwtAuth *auth.JWTAuth, username, password string) {
|
||
engine := r.Engine
|
||
|
||
// 全局中间件
|
||
engine.Use(middleware.Recovery())
|
||
engine.Use(middleware.Logger())
|
||
engine.Use(middleware.CORS())
|
||
|
||
// Swagger 文档
|
||
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||
|
||
// 认证路由 (无需 JWT)
|
||
authHandler := handler.NewAuthHandler(username, password, jwtAuth)
|
||
engine.POST("/api/auth/login", authHandler.Login)
|
||
engine.GET("/api/auth/check", authHandler.Check)
|
||
|
||
// API 路由 (需要 JWT)
|
||
api := engine.Group("/api")
|
||
api.Use(middleware.JWTAuth(jwtAuth))
|
||
{
|
||
// 状态
|
||
statusHandler := handler.NewStatusHandler(app)
|
||
api.GET("/status", statusHandler.GetStatus)
|
||
api.GET("/update/version", statusHandler.GetVersion)
|
||
|
||
// 客户端管理
|
||
clientHandler := handler.NewClientHandler(app)
|
||
api.GET("/clients", clientHandler.List)
|
||
api.POST("/clients", clientHandler.Create)
|
||
api.GET("/client/:id", clientHandler.Get)
|
||
api.PUT("/client/:id", clientHandler.Update)
|
||
api.DELETE("/client/:id", clientHandler.Delete)
|
||
api.POST("/client/:id/push", clientHandler.PushConfig)
|
||
api.POST("/client/:id/disconnect", clientHandler.Disconnect)
|
||
api.POST("/client/:id/restart", clientHandler.Restart)
|
||
api.POST("/client/:id/install-plugins", clientHandler.InstallPlugins)
|
||
api.POST("/client/:id/plugin/:pluginName/:action", clientHandler.PluginAction)
|
||
|
||
// 配置管理
|
||
configHandler := handler.NewConfigHandler(app)
|
||
api.GET("/config", configHandler.Get)
|
||
api.PUT("/config", configHandler.Update)
|
||
api.POST("/config/reload", configHandler.Reload)
|
||
|
||
// 插件管理
|
||
pluginHandler := handler.NewPluginHandler(app)
|
||
api.GET("/plugins", pluginHandler.List)
|
||
api.POST("/plugin/:name/enable", pluginHandler.Enable)
|
||
api.POST("/plugin/:name/disable", pluginHandler.Disable)
|
||
api.GET("/rule-schemas", pluginHandler.GetRuleSchemas)
|
||
api.GET("/client-plugin/:clientID/:pluginName/config", pluginHandler.GetClientConfig)
|
||
api.PUT("/client-plugin/:clientID/:pluginName/config", pluginHandler.UpdateClientConfig)
|
||
|
||
// JS 插件管理
|
||
jsPluginHandler := handler.NewJSPluginHandler(app)
|
||
api.GET("/js-plugins", jsPluginHandler.List)
|
||
api.POST("/js-plugins", jsPluginHandler.Create)
|
||
api.GET("/js-plugin/:name", jsPluginHandler.Get)
|
||
api.PUT("/js-plugin/:name", jsPluginHandler.Update)
|
||
api.DELETE("/js-plugin/:name", jsPluginHandler.Delete)
|
||
api.POST("/js-plugin/:name/push/:clientID", jsPluginHandler.PushToClient)
|
||
|
||
// 插件商店
|
||
storeHandler := handler.NewStoreHandler(app)
|
||
api.GET("/store/plugins", storeHandler.ListPlugins)
|
||
api.POST("/store/install", storeHandler.Install)
|
||
|
||
// 更新管理
|
||
updateHandler := handler.NewUpdateHandler(app)
|
||
api.GET("/update/check/server", updateHandler.CheckServer)
|
||
api.GET("/update/check/client", updateHandler.CheckClient)
|
||
api.POST("/update/apply/server", updateHandler.ApplyServer)
|
||
api.POST("/update/apply/client", updateHandler.ApplyClient)
|
||
|
||
// 日志管理
|
||
logHandler := handler.NewLogHandler(app)
|
||
api.GET("/client/:id/logs", logHandler.StreamLogs)
|
||
}
|
||
}
|
||
|
||
// SetupStaticFiles 配置静态文件处理
|
||
func (r *GinRouter) SetupStaticFiles(staticFS fs.FS) {
|
||
// 使用 NoRoute 处理 SPA 路由
|
||
r.Engine.NoRoute(gin.WrapH(&spaHandler{fs: http.FS(staticFS)}))
|
||
}
|
||
|
||
// spaHandler SPA 路由处理器
|
||
type spaHandler struct {
|
||
fs http.FileSystem
|
||
}
|
||
|
||
func (h *spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
path := r.URL.Path
|
||
|
||
// API 请求不应该返回 SPA 页面
|
||
if len(path) >= 4 && path[:4] == "/api" {
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.WriteHeader(http.StatusNotFound)
|
||
w.Write([]byte(`{"code":404,"message":"Not Found"}`))
|
||
return
|
||
}
|
||
|
||
// 尝试打开请求的文件
|
||
f, err := h.fs.Open(path)
|
||
if err != nil {
|
||
// 文件不存在时,检查是否是静态资源请求
|
||
// 静态资源(js, css, 图片等)应该返回 404,而不是 index.html
|
||
if isStaticAsset(path) {
|
||
http.Error(w, "Not Found", http.StatusNotFound)
|
||
return
|
||
}
|
||
// 其他路径返回 index.html(SPA 路由)
|
||
f, err = h.fs.Open("index.html")
|
||
if err != nil {
|
||
http.Error(w, "Not Found", http.StatusNotFound)
|
||
return
|
||
}
|
||
}
|
||
defer f.Close()
|
||
|
||
stat, err := f.Stat()
|
||
if err != nil {
|
||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
if stat.IsDir() {
|
||
f.Close()
|
||
f, err = h.fs.Open(path + "/index.html")
|
||
if err != nil {
|
||
f, _ = h.fs.Open("index.html")
|
||
}
|
||
stat, _ = f.Stat()
|
||
}
|
||
|
||
if seeker, ok := f.(io.ReadSeeker); ok {
|
||
http.ServeContent(w, r, path, stat.ModTime(), seeker)
|
||
}
|
||
}
|
||
|
||
// isStaticAsset 检查路径是否是静态资源
|
||
func isStaticAsset(path string) bool {
|
||
staticExtensions := []string{
|
||
".js", ".css", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico",
|
||
".woff", ".woff2", ".ttf", ".eot", ".map", ".json",
|
||
}
|
||
for _, ext := range staticExtensions {
|
||
if len(path) > len(ext) && path[len(path)-len(ext):] == ext {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// Re-export types from handler package for backward compatibility
|
||
type (
|
||
ServerInterface = handler.ServerInterface
|
||
AppInterface = handler.AppInterface
|
||
ConfigField = handler.ConfigField
|
||
RuleSchema = handler.RuleSchema
|
||
PluginInfo = handler.PluginInfo
|
||
JSPluginInstallRequest = handler.JSPluginInstallRequest
|
||
)
|