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 page 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]
|
||||
func (ec *EnvController) GetEnvVars(c *gin.Context) {
|
||||
userID := c.GetString("userID")
|
||||
|
||||
@@ -28,7 +28,7 @@ func NewLogController() *LogController {
|
||||
// @Param status query string false "状态"
|
||||
// @Param page 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]
|
||||
func (lc *LogController) GetLogs(c *gin.Context) {
|
||||
p := utils.ParsePagination(c)
|
||||
|
||||
@@ -330,11 +330,11 @@ func (sc *SettingsController) GetLoginLogs(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Success(c, gin.H{
|
||||
"data": vo.ToLoginLogVOListFromModels(logs),
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
utils.Success(c, utils.PaginationData{
|
||||
Data: vo.ToLoginLogVOListFromModels(logs),
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) {
|
||||
// @Param type query string false "任务类型"
|
||||
// @Param page 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]
|
||||
func (tc *TaskController) GetTasks(c *gin.Context) {
|
||||
p := utils.ParsePagination(c)
|
||||
|
||||
+111
-74
@@ -69,8 +69,50 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
root = router.Group("")
|
||||
}
|
||||
|
||||
initStaticRoutes(root)
|
||||
initOpenAPIRoutes(root, urlPrefix)
|
||||
|
||||
// API 路由组
|
||||
apiV1 := root.Group("/api/v1")
|
||||
initPublicAPIRoutes(apiV1, c)
|
||||
initAuthorizedAPIRoutes(apiV1, c)
|
||||
initAgentAPIRoutes(root, c)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func initStaticRoutes(root *gin.RouterGroup) {
|
||||
staticFS := static.GetFS()
|
||||
if staticFS != nil {
|
||||
if staticFS == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 静态资源服务(Vue SPA),带缓存头部
|
||||
assetsGroup := root.Group("/assets")
|
||||
assetsGroup.Use(cacheControl("public, max-age=31536000, immutable")) // 带哈希的资源缓存
|
||||
@@ -88,6 +130,7 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
})
|
||||
}
|
||||
|
||||
func initOpenAPIRoutes(root *gin.RouterGroup, urlPrefix string) {
|
||||
// OpenAPI documentation using Scalar UI (带 Basic Auth 认证)
|
||||
root.GET("/openapi/*any", func(c *gin.Context) {
|
||||
settingsSvc := services.NewSettingsService()
|
||||
@@ -112,14 +155,13 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
middleware.SwaggerAuth()(c)
|
||||
if c.IsAborted() {
|
||||
// 如果认证失败(且被中间件置为 404,如密码错误且我们想要隐藏它)
|
||||
if c.Writer.Status() == 404 {
|
||||
if c.Writer.Status() == http.StatusNotFound {
|
||||
serveSPA(c, urlPrefix, 404)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 获取内部路径并标准化(移除前后的所有斜杠)
|
||||
// c.Param("any") 对于 *any 匹配通常包含领先斜杠,如 "/index.html"
|
||||
path := strings.Trim(c.Param("any"), "/")
|
||||
|
||||
// 1. 根路径或空路径 -> 重定向到 index.html
|
||||
@@ -163,10 +205,9 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
// 其他未匹配路径 -> 返回 404 SPA 页面
|
||||
serveSPA(c, urlPrefix, 404)
|
||||
})
|
||||
}
|
||||
|
||||
// API 路由组
|
||||
api := root.Group("/api/v1")
|
||||
{
|
||||
func initPublicAPIRoutes(api *gin.RouterGroup, c *Controllers) {
|
||||
// Health check (无需认证)
|
||||
api.GET("/ping", func(ctx *gin.Context) {
|
||||
ctx.JSON(200, gin.H{"message": "pong"})
|
||||
@@ -182,8 +223,9 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
|
||||
// 公开的站点设置(无需认证)
|
||||
api.GET("/settings/public", c.Settings.GetPublicSiteSettings)
|
||||
}
|
||||
|
||||
// 需要认证的路由
|
||||
func initAuthorizedAPIRoutes(api *gin.RouterGroup, c *Controllers) {
|
||||
authorized := api.Group("")
|
||||
authorized.Use(middleware.AuthRequired())
|
||||
{
|
||||
@@ -196,8 +238,29 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
authorized.GET("/sendstats", c.Dashboard.GetSendStats)
|
||||
authorized.GET("/taskstats", c.Dashboard.GetTaskStats)
|
||||
|
||||
// 任务模块
|
||||
tasks := authorized.Group("/tasks")
|
||||
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)
|
||||
@@ -207,16 +270,16 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
tasks.POST("/stop/:logID", c.Task.StopTask)
|
||||
}
|
||||
|
||||
// 任务执行模块
|
||||
execution := authorized.Group("/execute")
|
||||
execution := g.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")
|
||||
func registerEnvRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||
env := g.Group("/env")
|
||||
{
|
||||
env.POST("", c.Env.CreateEnvVar)
|
||||
env.GET("", c.Env.GetEnvVars)
|
||||
@@ -226,9 +289,10 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
env.PUT("/:id", c.Env.UpdateEnvVar)
|
||||
env.DELETE("/:id", c.Env.DeleteEnvVar)
|
||||
}
|
||||
}
|
||||
|
||||
// 脚本模块
|
||||
scripts := authorized.Group("/scripts")
|
||||
func registerScriptRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||
scripts := g.Group("/scripts")
|
||||
{
|
||||
scripts.POST("", c.Script.CreateScript)
|
||||
scripts.GET("", c.Script.GetScripts)
|
||||
@@ -236,9 +300,10 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
scripts.PUT("/:id", c.Script.UpdateScript)
|
||||
scripts.DELETE("/:id", c.Script.DeleteScript)
|
||||
}
|
||||
}
|
||||
|
||||
// 文件管理模块
|
||||
files := authorized.Group("/files")
|
||||
func registerFileRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||
files := g.Group("/files")
|
||||
{
|
||||
files.GET("/tree", c.File.GetFileTree)
|
||||
files.GET("/content", c.File.GetFileContent)
|
||||
@@ -252,9 +317,10 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
files.POST("/upload", c.File.UploadArchive)
|
||||
files.POST("/uploadfiles", c.File.UploadFiles)
|
||||
}
|
||||
}
|
||||
|
||||
// 日志查看模块
|
||||
logs := authorized.Group("/logs")
|
||||
func registerLogRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||
logs := g.Group("/logs")
|
||||
{
|
||||
logs.GET("", c.Log.GetLogs)
|
||||
logs.POST("/clear", c.Log.ClearLogs)
|
||||
@@ -262,14 +328,16 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
// 设置中心模块
|
||||
settings := authorized.Group("/settings")
|
||||
func registerSettingsRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||
settings := g.Group("/settings")
|
||||
{
|
||||
settings.POST("/password", c.Settings.ChangePassword)
|
||||
settings.GET("/site", c.Settings.GetSiteSettings)
|
||||
@@ -289,9 +357,10 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
settings.GET("/:section/:key", c.Settings.GetSetting)
|
||||
settings.POST("/:section/:key/generate", c.Settings.GenerateSettingToken)
|
||||
}
|
||||
}
|
||||
|
||||
// Dependency routes (依赖管理)
|
||||
deps := authorized.Group("/deps")
|
||||
func registerDependencyRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||
deps := g.Group("/deps")
|
||||
{
|
||||
deps.GET("", c.Dependency.List)
|
||||
deps.POST("", c.Dependency.Create)
|
||||
@@ -304,9 +373,10 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
deps.POST("/reinstall-all-cmd", c.Dependency.GetReinstallAllCommand)
|
||||
deps.GET("/installed", c.Dependency.GetInstalled)
|
||||
}
|
||||
}
|
||||
|
||||
// Agent routes (Agent 管理)
|
||||
agents := authorized.Group("/agents")
|
||||
func registerAgentRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||
agents := g.Group("/agents")
|
||||
{
|
||||
agents.GET("", c.Agent.List)
|
||||
agents.GET("/version", c.Agent.GetVersion)
|
||||
@@ -320,8 +390,15 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
agents.DELETE("/tokens/:id", c.Agent.DeleteToken)
|
||||
}
|
||||
|
||||
// Mise routes (Mise 管理)
|
||||
mise := authorized.Group("/mise")
|
||||
// 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)
|
||||
@@ -329,15 +406,10 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
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")
|
||||
func registerNotificationRoutes(g *gin.RouterGroup, c *Controllers) {
|
||||
notify := g.Group("/notify")
|
||||
{
|
||||
notify.GET("/types", c.Notification.GetChannelTypes)
|
||||
notify.GET("/channels", c.Notification.GetChannels)
|
||||
@@ -350,14 +422,7 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// 通知发送 API(使用通知 Token 认证,供脚本调用)
|
||||
notifyAPI := api.Group("/notify")
|
||||
notifyAPI.Use(middleware.NotifyTokenAuth())
|
||||
{
|
||||
notifyAPI.POST("/send", c.Notification.SendNotification)
|
||||
}
|
||||
}
|
||||
|
||||
func initAgentAPIRoutes(root *gin.RouterGroup, c *Controllers) {
|
||||
// Agent API(供远程 Agent 调用,不使用 /v1 版本号)
|
||||
agentAPI := root.Group("/api/agent")
|
||||
{
|
||||
@@ -367,34 +432,6 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
agentAPI.GET("/download", c.Agent.Download) // 也在这里注册,兼容 Agent 调用
|
||||
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 给前端渲染
|
||||
|
||||
@@ -53,7 +53,7 @@ func (p Pagination) Offset() int {
|
||||
|
||||
// PaginationData 分页数据
|
||||
type PaginationData struct {
|
||||
List interface{} `json:"list"`
|
||||
Data interface{} `json:"data"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
@@ -62,7 +62,7 @@ type PaginationData struct {
|
||||
// PaginatedResponse 分页响应
|
||||
func PaginatedResponse(c *gin.Context, data interface{}, total int64, p Pagination) {
|
||||
Success(c, PaginationData{
|
||||
List: data,
|
||||
Data: data,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
|
||||
@@ -344,7 +344,7 @@ export interface ExecutionResult {
|
||||
}
|
||||
|
||||
export interface TaskListResponse {
|
||||
list: Task[]
|
||||
data: Task[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
@@ -365,7 +365,7 @@ export interface EnvVar {
|
||||
}
|
||||
|
||||
export interface EnvListResponse {
|
||||
list: EnvVar[]
|
||||
data: EnvVar[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
@@ -396,7 +396,7 @@ export interface TaskLog {
|
||||
}
|
||||
|
||||
export interface LogListResponse {
|
||||
list: TaskLog[]
|
||||
data: TaskLog[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
@@ -457,7 +457,7 @@ export interface LoginLog {
|
||||
}
|
||||
|
||||
export interface LoginLogListResponse {
|
||||
list: LoginLog[]
|
||||
data: LoginLog[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
|
||||
@@ -34,10 +34,10 @@ let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||
async function loadEnvVars() {
|
||||
try {
|
||||
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
|
||||
// 初始化显示状态,根据数据库的 hidden 状态同步显示
|
||||
res.list.forEach(env => {
|
||||
res.data.forEach(env => {
|
||||
showValues.value[env.id] = !env.hidden
|
||||
})
|
||||
} catch { toast.error('加载环境变量失败') }
|
||||
|
||||
@@ -76,7 +76,7 @@ async function loadLogs() {
|
||||
params.status = filterStatus.value
|
||||
}
|
||||
const response = await api.logs.list(params)
|
||||
logs.value = response.list
|
||||
logs.value = response.data
|
||||
total.value = response.total
|
||||
} catch {
|
||||
toast.error('加载日志失败')
|
||||
|
||||
@@ -81,7 +81,7 @@ async function loadLogs() {
|
||||
page_size: pageSize.value,
|
||||
username: filterUsername.value || undefined
|
||||
})
|
||||
logs.value = res.list
|
||||
logs.value = res.data
|
||||
total.value = res.total
|
||||
} catch {
|
||||
toast.error('加载登录日志失败')
|
||||
|
||||
@@ -74,7 +74,7 @@ async function loadTasks() {
|
||||
type: filterType.value === 'all' ? undefined : filterType.value,
|
||||
agent_id: filterAgentId.value || undefined
|
||||
})
|
||||
tasks.value = res.list
|
||||
tasks.value = res.data
|
||||
total.value = res.total
|
||||
} catch { toast.error('加载任务失败') }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user