feat: adjust openapi page laod
This commit is contained in:
@@ -62,7 +62,7 @@ func (ec *EnvController) CreateEnvVar(c *gin.Context) {
|
|||||||
// @Param name query string false "按名称模糊查询"
|
// @Param name query string false "按名称模糊查询"
|
||||||
// @Param page query int false "页码"
|
// @Param page query int false "页码"
|
||||||
// @Param page_size query int false "每页数量"
|
// @Param page_size query int false "每页数量"
|
||||||
// @Success 200 {object} utils.Response{data=utils.PaginationData{list=[]vo.EnvVO}}
|
// @Success 200 {object} utils.Response{data=utils.PaginationData{data=[]vo.EnvVO}}
|
||||||
// @Router /env [get]
|
// @Router /env [get]
|
||||||
func (ec *EnvController) GetEnvVars(c *gin.Context) {
|
func (ec *EnvController) GetEnvVars(c *gin.Context) {
|
||||||
userID := c.GetString("userID")
|
userID := c.GetString("userID")
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func NewLogController() *LogController {
|
|||||||
// @Param status query string false "状态"
|
// @Param status query string false "状态"
|
||||||
// @Param page query int false "页码"
|
// @Param page query int false "页码"
|
||||||
// @Param page_size query int false "每页数量"
|
// @Param page_size query int false "每页数量"
|
||||||
// @Success 200 {object} utils.Response{data=utils.PaginationData{list=[]vo.TaskLogVO}}
|
// @Success 200 {object} utils.Response{data=utils.PaginationData{data=[]vo.TaskLogVO}}
|
||||||
// @Router /logs [get]
|
// @Router /logs [get]
|
||||||
func (lc *LogController) GetLogs(c *gin.Context) {
|
func (lc *LogController) GetLogs(c *gin.Context) {
|
||||||
p := utils.ParsePagination(c)
|
p := utils.ParsePagination(c)
|
||||||
|
|||||||
@@ -330,11 +330,11 @@ func (sc *SettingsController) GetLoginLogs(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Success(c, gin.H{
|
utils.Success(c, utils.PaginationData{
|
||||||
"data": vo.ToLoginLogVOListFromModels(logs),
|
Data: vo.ToLoginLogVOListFromModels(logs),
|
||||||
"total": total,
|
Total: total,
|
||||||
"page": page,
|
Page: page,
|
||||||
"page_size": pageSize,
|
PageSize: pageSize,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) {
|
|||||||
// @Param type query string false "任务类型"
|
// @Param type query string false "任务类型"
|
||||||
// @Param page query int false "页码"
|
// @Param page query int false "页码"
|
||||||
// @Param page_size query int false "每页数量"
|
// @Param page_size query int false "每页数量"
|
||||||
// @Success 200 {object} utils.Response{data=utils.PaginationData{list=[]vo.TaskVO}}
|
// @Success 200 {object} utils.Response{data=utils.PaginationData{data=[]vo.TaskVO}}
|
||||||
// @Router /tasks [get]
|
// @Router /tasks [get]
|
||||||
func (tc *TaskController) GetTasks(c *gin.Context) {
|
func (tc *TaskController) GetTasks(c *gin.Context) {
|
||||||
p := utils.ParsePagination(c)
|
p := utils.ParsePagination(c)
|
||||||
|
|||||||
+274
-237
@@ -69,25 +69,68 @@ func Setup(c *Controllers) *gin.Engine {
|
|||||||
root = router.Group("")
|
root = router.Group("")
|
||||||
}
|
}
|
||||||
|
|
||||||
staticFS := static.GetFS()
|
initStaticRoutes(root)
|
||||||
if staticFS != nil {
|
initOpenAPIRoutes(root, urlPrefix)
|
||||||
// 静态资源服务(Vue SPA),带缓存头部
|
|
||||||
assetsGroup := root.Group("/assets")
|
|
||||||
assetsGroup.Use(cacheControl("public, max-age=31536000, immutable")) // 带哈希的资源缓存
|
|
||||||
assetsGroup.StaticFS("/", http.FS(mustSubFS(staticFS, "assets")))
|
|
||||||
|
|
||||||
// logo.svg 短缓存实现
|
// API 路由组
|
||||||
root.GET("/logo.svg", func(ctx *gin.Context) {
|
apiV1 := root.Group("/api/v1")
|
||||||
data, err := static.ReadFile("logo.svg")
|
initPublicAPIRoutes(apiV1, c)
|
||||||
if err != nil {
|
initAuthorizedAPIRoutes(apiV1, c)
|
||||||
ctx.Status(404)
|
initAgentAPIRoutes(root, c)
|
||||||
return
|
|
||||||
}
|
// SPA 兜底路由 - 返回 index.html(HTML禁用缓存以保证实时同步)
|
||||||
ctx.Header("Cache-Control", "public, max-age=86400") // 缓存1天
|
// 必须在最后注册,作为兜底路由
|
||||||
ctx.Data(200, "image/svg+xml", data)
|
router.NoRoute(func(ctx *gin.Context) {
|
||||||
})
|
path := ctx.Request.URL.Path
|
||||||
|
|
||||||
|
// 如果配置了前缀,只处理带前缀的路径
|
||||||
|
if urlPrefix != "" && !strings.HasPrefix(path, urlPrefix) {
|
||||||
|
ctx.Status(404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析实际的相对路径
|
||||||
|
relPath := strings.TrimPrefix(path, urlPrefix)
|
||||||
|
if !strings.HasPrefix(relPath, "/") {
|
||||||
|
relPath = "/" + relPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拦截属于 API 或静态资源目录下不存在的请求,绝不能返回 index.html 造成前端 MIME 错误
|
||||||
|
if strings.HasPrefix(relPath, "/api/") || strings.HasPrefix(relPath, "/assets/") {
|
||||||
|
ctx.String(404, "Not Found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serveSPA(ctx, urlPrefix, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
func initStaticRoutes(root *gin.RouterGroup) {
|
||||||
|
staticFS := static.GetFS()
|
||||||
|
if staticFS == nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 静态资源服务(Vue SPA),带缓存头部
|
||||||
|
assetsGroup := root.Group("/assets")
|
||||||
|
assetsGroup.Use(cacheControl("public, max-age=31536000, immutable")) // 带哈希的资源缓存
|
||||||
|
assetsGroup.StaticFS("/", http.FS(mustSubFS(staticFS, "assets")))
|
||||||
|
|
||||||
|
// logo.svg 短缓存实现
|
||||||
|
root.GET("/logo.svg", func(ctx *gin.Context) {
|
||||||
|
data, err := static.ReadFile("logo.svg")
|
||||||
|
if err != nil {
|
||||||
|
ctx.Status(404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Header("Cache-Control", "public, max-age=86400") // 缓存1天
|
||||||
|
ctx.Data(200, "image/svg+xml", data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func initOpenAPIRoutes(root *gin.RouterGroup, urlPrefix string) {
|
||||||
// OpenAPI documentation using Scalar UI (带 Basic Auth 认证)
|
// OpenAPI documentation using Scalar UI (带 Basic Auth 认证)
|
||||||
root.GET("/openapi/*any", func(c *gin.Context) {
|
root.GET("/openapi/*any", func(c *gin.Context) {
|
||||||
settingsSvc := services.NewSettingsService()
|
settingsSvc := services.NewSettingsService()
|
||||||
@@ -112,14 +155,13 @@ func Setup(c *Controllers) *gin.Engine {
|
|||||||
middleware.SwaggerAuth()(c)
|
middleware.SwaggerAuth()(c)
|
||||||
if c.IsAborted() {
|
if c.IsAborted() {
|
||||||
// 如果认证失败(且被中间件置为 404,如密码错误且我们想要隐藏它)
|
// 如果认证失败(且被中间件置为 404,如密码错误且我们想要隐藏它)
|
||||||
if c.Writer.Status() == 404 {
|
if c.Writer.Status() == http.StatusNotFound {
|
||||||
serveSPA(c, urlPrefix, 404)
|
serveSPA(c, urlPrefix, 404)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取内部路径并标准化(移除前后的所有斜杠)
|
// 获取内部路径并标准化(移除前后的所有斜杠)
|
||||||
// c.Param("any") 对于 *any 匹配通常包含领先斜杠,如 "/index.html"
|
|
||||||
path := strings.Trim(c.Param("any"), "/")
|
path := strings.Trim(c.Param("any"), "/")
|
||||||
|
|
||||||
// 1. 根路径或空路径 -> 重定向到 index.html
|
// 1. 根路径或空路径 -> 重定向到 index.html
|
||||||
@@ -163,201 +205,224 @@ func Setup(c *Controllers) *gin.Engine {
|
|||||||
// 其他未匹配路径 -> 返回 404 SPA 页面
|
// 其他未匹配路径 -> 返回 404 SPA 页面
|
||||||
serveSPA(c, urlPrefix, 404)
|
serveSPA(c, urlPrefix, 404)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// API 路由组
|
func initPublicAPIRoutes(api *gin.RouterGroup, c *Controllers) {
|
||||||
api := root.Group("/api/v1")
|
// Health check (无需认证)
|
||||||
|
api.GET("/ping", func(ctx *gin.Context) {
|
||||||
|
ctx.JSON(200, gin.H{"message": "pong"})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Authentication routes (无需认证)
|
||||||
|
auth := api.Group("/auth")
|
||||||
{
|
{
|
||||||
// Health check (无需认证)
|
auth.POST("/login", c.Auth.Login)
|
||||||
api.GET("/ping", func(ctx *gin.Context) {
|
auth.POST("/logout", c.Auth.Logout)
|
||||||
ctx.JSON(200, gin.H{"message": "pong"})
|
auth.POST("/register", c.Auth.Register)
|
||||||
})
|
|
||||||
|
|
||||||
// Authentication routes (无需认证)
|
|
||||||
auth := api.Group("/auth")
|
|
||||||
{
|
|
||||||
auth.POST("/login", c.Auth.Login)
|
|
||||||
auth.POST("/logout", c.Auth.Logout)
|
|
||||||
auth.POST("/register", c.Auth.Register)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 公开的站点设置(无需认证)
|
|
||||||
api.GET("/settings/public", c.Settings.GetPublicSiteSettings)
|
|
||||||
|
|
||||||
// 需要认证的路由
|
|
||||||
authorized := api.Group("")
|
|
||||||
authorized.Use(middleware.AuthRequired())
|
|
||||||
{
|
|
||||||
// 获取当前用户
|
|
||||||
authorized.GET("/auth/me", c.Auth.GetCurrentUser)
|
|
||||||
|
|
||||||
// 仪表盘统计
|
|
||||||
authorized.GET("/stats", c.Dashboard.GetStats)
|
|
||||||
authorized.GET("/sentence", c.Dashboard.GetSentence)
|
|
||||||
authorized.GET("/sendstats", c.Dashboard.GetSendStats)
|
|
||||||
authorized.GET("/taskstats", c.Dashboard.GetTaskStats)
|
|
||||||
|
|
||||||
// 任务模块
|
|
||||||
tasks := authorized.Group("/tasks")
|
|
||||||
{
|
|
||||||
tasks.POST("", c.Task.CreateTask)
|
|
||||||
tasks.GET("", c.Task.GetTasks)
|
|
||||||
tasks.GET("/:id", c.Task.GetTask)
|
|
||||||
tasks.PUT("/:id", c.Task.UpdateTask)
|
|
||||||
tasks.DELETE("/:id", c.Task.DeleteTask)
|
|
||||||
tasks.POST("/stop/:logID", c.Task.StopTask)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 任务执行模块
|
|
||||||
execution := authorized.Group("/execute")
|
|
||||||
{
|
|
||||||
execution.POST("/task/:id", c.Executor.ExecuteTask)
|
|
||||||
execution.POST("/command", c.Executor.ExecuteCommand)
|
|
||||||
execution.GET("/results", c.Executor.GetLastResults)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 环境变量模块
|
|
||||||
env := authorized.Group("/env")
|
|
||||||
{
|
|
||||||
env.POST("", c.Env.CreateEnvVar)
|
|
||||||
env.GET("", c.Env.GetEnvVars)
|
|
||||||
env.GET("/all", c.Env.GetAllEnvVars)
|
|
||||||
env.GET("/:id", c.Env.GetEnvVar)
|
|
||||||
env.GET("/:id/tasks", c.Env.GetAssociatedTasks)
|
|
||||||
env.PUT("/:id", c.Env.UpdateEnvVar)
|
|
||||||
env.DELETE("/:id", c.Env.DeleteEnvVar)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 脚本模块
|
|
||||||
scripts := authorized.Group("/scripts")
|
|
||||||
{
|
|
||||||
scripts.POST("", c.Script.CreateScript)
|
|
||||||
scripts.GET("", c.Script.GetScripts)
|
|
||||||
scripts.GET("/:id", c.Script.GetScript)
|
|
||||||
scripts.PUT("/:id", c.Script.UpdateScript)
|
|
||||||
scripts.DELETE("/:id", c.Script.DeleteScript)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文件管理模块
|
|
||||||
files := authorized.Group("/files")
|
|
||||||
{
|
|
||||||
files.GET("/tree", c.File.GetFileTree)
|
|
||||||
files.GET("/content", c.File.GetFileContent)
|
|
||||||
files.GET("/download", c.File.DownloadFile)
|
|
||||||
files.POST("/content", c.File.SaveFileContent)
|
|
||||||
files.POST("/create", c.File.CreateFile)
|
|
||||||
files.POST("/delete", c.File.DeleteFile)
|
|
||||||
files.POST("/rename", c.File.RenameFile)
|
|
||||||
files.POST("/move", c.File.MoveFile)
|
|
||||||
files.POST("/copy", c.File.CopyFile)
|
|
||||||
files.POST("/upload", c.File.UploadArchive)
|
|
||||||
files.POST("/uploadfiles", c.File.UploadFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 日志查看模块
|
|
||||||
logs := authorized.Group("/logs")
|
|
||||||
{
|
|
||||||
logs.GET("", c.Log.GetLogs)
|
|
||||||
logs.POST("/clear", c.Log.ClearLogs)
|
|
||||||
logs.GET("/ws", c.LogWS.StreamLog)
|
|
||||||
logs.GET("/:id", c.Log.GetLogDetail)
|
|
||||||
logs.DELETE("/:id", c.Log.DeleteLog)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 终端模块
|
|
||||||
authorized.GET("/terminal/ws", c.Terminal.HandleWebSocket)
|
|
||||||
authorized.POST("/terminal/exec", c.Terminal.ExecuteShellCommand)
|
|
||||||
authorized.GET("/terminal/cmds", c.Terminal.GetCommands)
|
|
||||||
|
|
||||||
// 设置中心模块
|
|
||||||
settings := authorized.Group("/settings")
|
|
||||||
{
|
|
||||||
settings.POST("/password", c.Settings.ChangePassword)
|
|
||||||
settings.GET("/site", c.Settings.GetSiteSettings)
|
|
||||||
settings.PUT("/site", c.Settings.UpdateSiteSettings)
|
|
||||||
settings.POST("/site/api-token/generate", c.Settings.GenerateApiToken)
|
|
||||||
settings.POST("/site/openapi-token/generate", c.Settings.GenerateOpenapiToken)
|
|
||||||
settings.GET("/paths", c.Settings.GetPaths)
|
|
||||||
settings.GET("/scheduler", c.Settings.GetSchedulerSettings)
|
|
||||||
settings.PUT("/scheduler", c.Settings.UpdateSchedulerSettings)
|
|
||||||
settings.GET("/about", c.Settings.GetAbout)
|
|
||||||
settings.GET("/loginlogs", c.Settings.GetLoginLogs)
|
|
||||||
settings.POST("/backup", c.Settings.CreateBackup)
|
|
||||||
settings.GET("/backup/status", c.Settings.GetBackupStatus)
|
|
||||||
settings.GET("/backup/download", c.Settings.DownloadBackup)
|
|
||||||
settings.POST("/restore", c.Settings.RestoreBackup)
|
|
||||||
// 通用设置接口
|
|
||||||
settings.GET("/:section/:key", c.Settings.GetSetting)
|
|
||||||
settings.POST("/:section/:key/generate", c.Settings.GenerateSettingToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dependency routes (依赖管理)
|
|
||||||
deps := authorized.Group("/deps")
|
|
||||||
{
|
|
||||||
deps.GET("", c.Dependency.List)
|
|
||||||
deps.POST("", c.Dependency.Create)
|
|
||||||
deps.DELETE("/:id", c.Dependency.Delete)
|
|
||||||
deps.POST("/install", c.Dependency.Install)
|
|
||||||
deps.POST("/install-cmd", c.Dependency.GetInstallCommand)
|
|
||||||
deps.POST("/uninstall/:id", c.Dependency.Uninstall)
|
|
||||||
deps.POST("/reinstall/:id", c.Dependency.Reinstall)
|
|
||||||
deps.POST("/reinstall-all", c.Dependency.ReinstallAll)
|
|
||||||
deps.POST("/reinstall-all-cmd", c.Dependency.GetReinstallAllCommand)
|
|
||||||
deps.GET("/installed", c.Dependency.GetInstalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Agent routes (Agent 管理)
|
|
||||||
agents := authorized.Group("/agents")
|
|
||||||
{
|
|
||||||
agents.GET("", c.Agent.List)
|
|
||||||
agents.GET("/version", c.Agent.GetVersion)
|
|
||||||
agents.PUT("/:id", c.Agent.Update)
|
|
||||||
agents.DELETE("/:id", c.Agent.Delete)
|
|
||||||
agents.POST("/:id/token", c.Agent.RegenerateToken)
|
|
||||||
agents.POST("/:id/update", c.Agent.ForceUpdate)
|
|
||||||
// 令牌管理
|
|
||||||
agents.GET("/tokens", c.Agent.ListTokens)
|
|
||||||
agents.POST("/tokens", c.Agent.CreateToken)
|
|
||||||
agents.DELETE("/tokens/:id", c.Agent.DeleteToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mise routes (Mise 管理)
|
|
||||||
mise := authorized.Group("/mise")
|
|
||||||
{
|
|
||||||
mise.GET("/ls", c.Mise.List)
|
|
||||||
mise.POST("/sync", c.Mise.Sync)
|
|
||||||
mise.GET("/plugins", c.Mise.Plugins)
|
|
||||||
mise.GET("/versions", c.Mise.Versions)
|
|
||||||
mise.GET("/verify-cmd", c.Mise.VerifyCommand)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Agent API(供前端调用,保持在 v1 下)
|
|
||||||
agentAPIv1 := authorized.Group("/agent")
|
|
||||||
{
|
|
||||||
agentAPIv1.GET("/download", c.Agent.Download)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通知推送模块
|
|
||||||
notify := authorized.Group("/notify")
|
|
||||||
{
|
|
||||||
notify.GET("/types", c.Notification.GetChannelTypes)
|
|
||||||
notify.GET("/channels", c.Notification.GetChannels)
|
|
||||||
notify.POST("/channels", c.Notification.SaveChannel)
|
|
||||||
notify.DELETE("/channels/:id", c.Notification.DeleteChannel)
|
|
||||||
notify.POST("/channels/test", c.Notification.TestChannel)
|
|
||||||
notify.GET("/bindings", c.Notification.GetBindings)
|
|
||||||
notify.POST("/bindings", c.Notification.SaveBinding)
|
|
||||||
notify.DELETE("/bindings/:id", c.Notification.DeleteBinding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通知发送 API(使用通知 Token 认证,供脚本调用)
|
|
||||||
notifyAPI := api.Group("/notify")
|
|
||||||
notifyAPI.Use(middleware.NotifyTokenAuth())
|
|
||||||
{
|
|
||||||
notifyAPI.POST("/send", c.Notification.SendNotification)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 公开的站点设置(无需认证)
|
||||||
|
api.GET("/settings/public", c.Settings.GetPublicSiteSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initAuthorizedAPIRoutes(api *gin.RouterGroup, c *Controllers) {
|
||||||
|
authorized := api.Group("")
|
||||||
|
authorized.Use(middleware.AuthRequired())
|
||||||
|
{
|
||||||
|
// 获取当前用户
|
||||||
|
authorized.GET("/auth/me", c.Auth.GetCurrentUser)
|
||||||
|
|
||||||
|
// 仪表盘统计
|
||||||
|
authorized.GET("/stats", c.Dashboard.GetStats)
|
||||||
|
authorized.GET("/sentence", c.Dashboard.GetSentence)
|
||||||
|
authorized.GET("/sendstats", c.Dashboard.GetSendStats)
|
||||||
|
authorized.GET("/taskstats", c.Dashboard.GetTaskStats)
|
||||||
|
|
||||||
|
registerTaskRoutes(authorized, c)
|
||||||
|
registerEnvRoutes(authorized, c)
|
||||||
|
registerScriptRoutes(authorized, c)
|
||||||
|
registerFileRoutes(authorized, c)
|
||||||
|
registerLogRoutes(authorized, c)
|
||||||
|
registerTerminalRoutes(authorized, c)
|
||||||
|
registerSettingsRoutes(authorized, c)
|
||||||
|
registerDependencyRoutes(authorized, c)
|
||||||
|
registerAgentRoutes(authorized, c)
|
||||||
|
registerMiseRoutes(authorized, c)
|
||||||
|
registerNotificationRoutes(authorized, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知发送 API(使用通知 Token 认证,供脚本调用)
|
||||||
|
notifyAPI := api.Group("/notify")
|
||||||
|
notifyAPI.Use(middleware.NotifyTokenAuth())
|
||||||
|
{
|
||||||
|
notifyAPI.POST("/send", c.Notification.SendNotification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerTaskRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||||
|
tasks := g.Group("/tasks")
|
||||||
|
{
|
||||||
|
tasks.POST("", c.Task.CreateTask)
|
||||||
|
tasks.GET("", c.Task.GetTasks)
|
||||||
|
tasks.GET("/:id", c.Task.GetTask)
|
||||||
|
tasks.PUT("/:id", c.Task.UpdateTask)
|
||||||
|
tasks.DELETE("/:id", c.Task.DeleteTask)
|
||||||
|
tasks.POST("/stop/:logID", c.Task.StopTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
execution := g.Group("/execute")
|
||||||
|
{
|
||||||
|
execution.POST("/task/:id", c.Executor.ExecuteTask)
|
||||||
|
execution.POST("/command", c.Executor.ExecuteCommand)
|
||||||
|
execution.GET("/results", c.Executor.GetLastResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerEnvRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||||
|
env := g.Group("/env")
|
||||||
|
{
|
||||||
|
env.POST("", c.Env.CreateEnvVar)
|
||||||
|
env.GET("", c.Env.GetEnvVars)
|
||||||
|
env.GET("/all", c.Env.GetAllEnvVars)
|
||||||
|
env.GET("/:id", c.Env.GetEnvVar)
|
||||||
|
env.GET("/:id/tasks", c.Env.GetAssociatedTasks)
|
||||||
|
env.PUT("/:id", c.Env.UpdateEnvVar)
|
||||||
|
env.DELETE("/:id", c.Env.DeleteEnvVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerScriptRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||||
|
scripts := g.Group("/scripts")
|
||||||
|
{
|
||||||
|
scripts.POST("", c.Script.CreateScript)
|
||||||
|
scripts.GET("", c.Script.GetScripts)
|
||||||
|
scripts.GET("/:id", c.Script.GetScript)
|
||||||
|
scripts.PUT("/:id", c.Script.UpdateScript)
|
||||||
|
scripts.DELETE("/:id", c.Script.DeleteScript)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerFileRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||||
|
files := g.Group("/files")
|
||||||
|
{
|
||||||
|
files.GET("/tree", c.File.GetFileTree)
|
||||||
|
files.GET("/content", c.File.GetFileContent)
|
||||||
|
files.GET("/download", c.File.DownloadFile)
|
||||||
|
files.POST("/content", c.File.SaveFileContent)
|
||||||
|
files.POST("/create", c.File.CreateFile)
|
||||||
|
files.POST("/delete", c.File.DeleteFile)
|
||||||
|
files.POST("/rename", c.File.RenameFile)
|
||||||
|
files.POST("/move", c.File.MoveFile)
|
||||||
|
files.POST("/copy", c.File.CopyFile)
|
||||||
|
files.POST("/upload", c.File.UploadArchive)
|
||||||
|
files.POST("/uploadfiles", c.File.UploadFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerLogRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||||
|
logs := g.Group("/logs")
|
||||||
|
{
|
||||||
|
logs.GET("", c.Log.GetLogs)
|
||||||
|
logs.POST("/clear", c.Log.ClearLogs)
|
||||||
|
logs.GET("/ws", c.LogWS.StreamLog)
|
||||||
|
logs.GET("/:id", c.Log.GetLogDetail)
|
||||||
|
logs.DELETE("/:id", c.Log.DeleteLog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerTerminalRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||||
|
g.GET("/terminal/ws", c.Terminal.HandleWebSocket)
|
||||||
|
g.POST("/terminal/exec", c.Terminal.ExecuteShellCommand)
|
||||||
|
g.GET("/terminal/cmds", c.Terminal.GetCommands)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerSettingsRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||||
|
settings := g.Group("/settings")
|
||||||
|
{
|
||||||
|
settings.POST("/password", c.Settings.ChangePassword)
|
||||||
|
settings.GET("/site", c.Settings.GetSiteSettings)
|
||||||
|
settings.PUT("/site", c.Settings.UpdateSiteSettings)
|
||||||
|
settings.POST("/site/api-token/generate", c.Settings.GenerateApiToken)
|
||||||
|
settings.POST("/site/openapi-token/generate", c.Settings.GenerateOpenapiToken)
|
||||||
|
settings.GET("/paths", c.Settings.GetPaths)
|
||||||
|
settings.GET("/scheduler", c.Settings.GetSchedulerSettings)
|
||||||
|
settings.PUT("/scheduler", c.Settings.UpdateSchedulerSettings)
|
||||||
|
settings.GET("/about", c.Settings.GetAbout)
|
||||||
|
settings.GET("/loginlogs", c.Settings.GetLoginLogs)
|
||||||
|
settings.POST("/backup", c.Settings.CreateBackup)
|
||||||
|
settings.GET("/backup/status", c.Settings.GetBackupStatus)
|
||||||
|
settings.GET("/backup/download", c.Settings.DownloadBackup)
|
||||||
|
settings.POST("/restore", c.Settings.RestoreBackup)
|
||||||
|
// 通用设置接口
|
||||||
|
settings.GET("/:section/:key", c.Settings.GetSetting)
|
||||||
|
settings.POST("/:section/:key/generate", c.Settings.GenerateSettingToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerDependencyRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||||
|
deps := g.Group("/deps")
|
||||||
|
{
|
||||||
|
deps.GET("", c.Dependency.List)
|
||||||
|
deps.POST("", c.Dependency.Create)
|
||||||
|
deps.DELETE("/:id", c.Dependency.Delete)
|
||||||
|
deps.POST("/install", c.Dependency.Install)
|
||||||
|
deps.POST("/install-cmd", c.Dependency.GetInstallCommand)
|
||||||
|
deps.POST("/uninstall/:id", c.Dependency.Uninstall)
|
||||||
|
deps.POST("/reinstall/:id", c.Dependency.Reinstall)
|
||||||
|
deps.POST("/reinstall-all", c.Dependency.ReinstallAll)
|
||||||
|
deps.POST("/reinstall-all-cmd", c.Dependency.GetReinstallAllCommand)
|
||||||
|
deps.GET("/installed", c.Dependency.GetInstalled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerAgentRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||||
|
agents := g.Group("/agents")
|
||||||
|
{
|
||||||
|
agents.GET("", c.Agent.List)
|
||||||
|
agents.GET("/version", c.Agent.GetVersion)
|
||||||
|
agents.PUT("/:id", c.Agent.Update)
|
||||||
|
agents.DELETE("/:id", c.Agent.Delete)
|
||||||
|
agents.POST("/:id/token", c.Agent.RegenerateToken)
|
||||||
|
agents.POST("/:id/update", c.Agent.ForceUpdate)
|
||||||
|
// 令牌管理
|
||||||
|
agents.GET("/tokens", c.Agent.ListTokens)
|
||||||
|
agents.POST("/tokens", c.Agent.CreateToken)
|
||||||
|
agents.DELETE("/tokens/:id", c.Agent.DeleteToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agent API(供前端调用,保持在 v1 下)
|
||||||
|
agentAPIv1 := g.Group("/agent")
|
||||||
|
{
|
||||||
|
agentAPIv1.GET("/download", c.Agent.Download)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerMiseRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||||
|
mise := g.Group("/mise")
|
||||||
|
{
|
||||||
|
mise.GET("/ls", c.Mise.List)
|
||||||
|
mise.POST("/sync", c.Mise.Sync)
|
||||||
|
mise.GET("/plugins", c.Mise.Plugins)
|
||||||
|
mise.GET("/versions", c.Mise.Versions)
|
||||||
|
mise.GET("/verify-cmd", c.Mise.VerifyCommand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerNotificationRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||||
|
notify := g.Group("/notify")
|
||||||
|
{
|
||||||
|
notify.GET("/types", c.Notification.GetChannelTypes)
|
||||||
|
notify.GET("/channels", c.Notification.GetChannels)
|
||||||
|
notify.POST("/channels", c.Notification.SaveChannel)
|
||||||
|
notify.DELETE("/channels/:id", c.Notification.DeleteChannel)
|
||||||
|
notify.POST("/channels/test", c.Notification.TestChannel)
|
||||||
|
notify.GET("/bindings", c.Notification.GetBindings)
|
||||||
|
notify.POST("/bindings", c.Notification.SaveBinding)
|
||||||
|
notify.DELETE("/bindings/:id", c.Notification.DeleteBinding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initAgentAPIRoutes(root *gin.RouterGroup, c *Controllers) {
|
||||||
// Agent API(供远程 Agent 调用,不使用 /v1 版本号)
|
// Agent API(供远程 Agent 调用,不使用 /v1 版本号)
|
||||||
agentAPI := root.Group("/api/agent")
|
agentAPI := root.Group("/api/agent")
|
||||||
{
|
{
|
||||||
@@ -367,34 +432,6 @@ func Setup(c *Controllers) *gin.Engine {
|
|||||||
agentAPI.GET("/download", c.Agent.Download) // 也在这里注册,兼容 Agent 调用
|
agentAPI.GET("/download", c.Agent.Download) // 也在这里注册,兼容 Agent 调用
|
||||||
agentAPI.GET("/ws", c.Agent.WSConnect) // WebSocket 连接
|
agentAPI.GET("/ws", c.Agent.WSConnect) // WebSocket 连接
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPA 兜底路由 - 返回 index.html(HTML禁用缓存以保证实时同步)
|
|
||||||
// 必须在最后注册,作为兜底路由
|
|
||||||
router.NoRoute(func(ctx *gin.Context) {
|
|
||||||
path := ctx.Request.URL.Path
|
|
||||||
|
|
||||||
// 如果配置了前缀,只处理带前缀的路径
|
|
||||||
if urlPrefix != "" && !strings.HasPrefix(path, urlPrefix) {
|
|
||||||
ctx.Status(404)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析实际的相对路径
|
|
||||||
relPath := strings.TrimPrefix(path, urlPrefix)
|
|
||||||
if !strings.HasPrefix(relPath, "/") {
|
|
||||||
relPath = "/" + relPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// 拦截属于 API 或静态资源目录下不存在的请求,绝不能返回 index.html 造成前端 MIME 错误
|
|
||||||
if strings.HasPrefix(relPath, "/api/") || strings.HasPrefix(relPath, "/assets/") {
|
|
||||||
ctx.String(404, "Not Found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
serveSPA(ctx, urlPrefix, 200)
|
|
||||||
})
|
|
||||||
|
|
||||||
return router
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveSPA 注入配置并返回 index.html 给前端渲染
|
// serveSPA 注入配置并返回 index.html 给前端渲染
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func (p Pagination) Offset() int {
|
|||||||
|
|
||||||
// PaginationData 分页数据
|
// PaginationData 分页数据
|
||||||
type PaginationData struct {
|
type PaginationData struct {
|
||||||
List interface{} `json:"list"`
|
Data interface{} `json:"data"`
|
||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
PageSize int `json:"page_size"`
|
PageSize int `json:"page_size"`
|
||||||
@@ -62,7 +62,7 @@ type PaginationData struct {
|
|||||||
// PaginatedResponse 分页响应
|
// PaginatedResponse 分页响应
|
||||||
func PaginatedResponse(c *gin.Context, data interface{}, total int64, p Pagination) {
|
func PaginatedResponse(c *gin.Context, data interface{}, total int64, p Pagination) {
|
||||||
Success(c, PaginationData{
|
Success(c, PaginationData{
|
||||||
List: data,
|
Data: data,
|
||||||
Total: total,
|
Total: total,
|
||||||
Page: p.Page,
|
Page: p.Page,
|
||||||
PageSize: p.PageSize,
|
PageSize: p.PageSize,
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ export interface ExecutionResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskListResponse {
|
export interface TaskListResponse {
|
||||||
list: Task[]
|
data: Task[]
|
||||||
total: number
|
total: number
|
||||||
page: number
|
page: number
|
||||||
page_size: number
|
page_size: number
|
||||||
@@ -365,7 +365,7 @@ export interface EnvVar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EnvListResponse {
|
export interface EnvListResponse {
|
||||||
list: EnvVar[]
|
data: EnvVar[]
|
||||||
total: number
|
total: number
|
||||||
page: number
|
page: number
|
||||||
page_size: number
|
page_size: number
|
||||||
@@ -396,7 +396,7 @@ export interface TaskLog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LogListResponse {
|
export interface LogListResponse {
|
||||||
list: TaskLog[]
|
data: TaskLog[]
|
||||||
total: number
|
total: number
|
||||||
page: number
|
page: number
|
||||||
page_size: number
|
page_size: number
|
||||||
@@ -457,7 +457,7 @@ export interface LoginLog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginLogListResponse {
|
export interface LoginLogListResponse {
|
||||||
list: LoginLog[]
|
data: LoginLog[]
|
||||||
total: number
|
total: number
|
||||||
page: number
|
page: number
|
||||||
page_size: number
|
page_size: number
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ let searchTimer: ReturnType<typeof setTimeout> | null = null
|
|||||||
async function loadEnvVars() {
|
async function loadEnvVars() {
|
||||||
try {
|
try {
|
||||||
const res = await api.env.list({ page: currentPage.value, page_size: pageSize.value, name: filterName.value || undefined })
|
const res = await api.env.list({ page: currentPage.value, page_size: pageSize.value, name: filterName.value || undefined })
|
||||||
envVars.value = res.list
|
envVars.value = res.data
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
// 初始化显示状态,根据数据库的 hidden 状态同步显示
|
// 初始化显示状态,根据数据库的 hidden 状态同步显示
|
||||||
res.list.forEach(env => {
|
res.data.forEach(env => {
|
||||||
showValues.value[env.id] = !env.hidden
|
showValues.value[env.id] = !env.hidden
|
||||||
})
|
})
|
||||||
} catch { toast.error('加载环境变量失败') }
|
} catch { toast.error('加载环境变量失败') }
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ async function loadLogs() {
|
|||||||
params.status = filterStatus.value
|
params.status = filterStatus.value
|
||||||
}
|
}
|
||||||
const response = await api.logs.list(params)
|
const response = await api.logs.list(params)
|
||||||
logs.value = response.list
|
logs.value = response.data
|
||||||
total.value = response.total
|
total.value = response.total
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('加载日志失败')
|
toast.error('加载日志失败')
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ async function loadLogs() {
|
|||||||
page_size: pageSize.value,
|
page_size: pageSize.value,
|
||||||
username: filterUsername.value || undefined
|
username: filterUsername.value || undefined
|
||||||
})
|
})
|
||||||
logs.value = res.list
|
logs.value = res.data
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('加载登录日志失败')
|
toast.error('加载登录日志失败')
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ async function loadTasks() {
|
|||||||
type: filterType.value === 'all' ? undefined : filterType.value,
|
type: filterType.value === 'all' ? undefined : filterType.value,
|
||||||
agent_id: filterAgentId.value || undefined
|
agent_id: filterAgentId.value || undefined
|
||||||
})
|
})
|
||||||
tasks.value = res.list
|
tasks.value = res.data
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
} catch { toast.error('加载任务失败') }
|
} catch { toast.error('加载任务失败') }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user