feat: openapi supoort
This commit is contained in:
@@ -29,6 +29,9 @@ jobs:
|
||||
- name: Build Web
|
||||
run: make build-web
|
||||
|
||||
- name: Generate Swagger
|
||||
run: make swag
|
||||
|
||||
- name: Build Binaries
|
||||
run: |
|
||||
VERSION=${{ github.ref_name }}
|
||||
|
||||
@@ -111,7 +111,7 @@ deps:
|
||||
|
||||
# Generate swagger documentation
|
||||
swag:
|
||||
go run github.com/swaggo/swag/cmd/swag@latest init -g main.go -o ./docs
|
||||
go run github.com/swaggo/swag/cmd/swag@latest init -g main.go -o ./openapi_docs
|
||||
|
||||
# Docker build
|
||||
docker-build:
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
### 最近更新
|
||||
|
||||
**2026.03.05** - **API 文档重构** 重构 OpenAPI 认证体系,支持站点级 Token 配置与 Basic Auth 保护;新增设计感十足的全局 **404 页面**。
|
||||
**2026.03.04** - 新增内置消息推送系统:全新原生支持企业微信、钉钉、飞书、Telegram、Bark、邮件等十余种主流渠道的推送,接入系统级事件通知自动捕获,告别原有必配外部推送服务的繁琐历史
|
||||
**2026.02.13** - 重构任务执行引擎:深度集成 Mise 运行时管理,支持 Python, Node.js, Go, Rust, PHP 等几乎所有主流语言的动态安装与多版本切换,同步上线跨语言统一依赖管理系统
|
||||
**2026.02.11** - 增强安全性:首次启动使用随机密码并打印在日志中,登录接口增加防暴力破解,文件系统操作增加路径穿越锁定
|
||||
|
||||
@@ -30,10 +30,4 @@ table_prefix = baihu_
|
||||
# 如果你手动设置了此项,它将覆盖数据库中的设置。
|
||||
secret =
|
||||
|
||||
[swagger]
|
||||
# 是否开启 Swagger API 文档 (开发测试建议开启,生产环境建议关闭或设置强密码)
|
||||
enabled = true
|
||||
# Swagger 文档 Basic Auth 账号
|
||||
user = admin
|
||||
# Swagger 文档 Basic Auth 密码
|
||||
password = swagger_password
|
||||
|
||||
|
||||
@@ -57,6 +57,9 @@ COPY . .
|
||||
# Copy frontend dist (needed for embedding)
|
||||
COPY --from=frontend-builder /app/web/dist ./internal/static/dist
|
||||
|
||||
# Generate Swagger
|
||||
RUN go run github.com/swaggo/swag/cmd/swag@latest init -g main.go -o ./openapi_docs
|
||||
|
||||
# Build Go binary
|
||||
RUN VERSION_VAL=$(cat /build-info/version.txt) && \
|
||||
BUILD_TIME_VAL=$(cat /build-info/build_time.txt) && \
|
||||
|
||||
@@ -37,12 +37,13 @@ const (
|
||||
SectionNotify = "notify"
|
||||
|
||||
// Site Settings Key 常量
|
||||
KeyTitle = "title"
|
||||
KeySubtitle = "subtitle"
|
||||
KeyIcon = "icon"
|
||||
KeyPageSize = "page_size"
|
||||
KeyCookieDays = "cookie_days"
|
||||
KeyApiToken = "api_token"
|
||||
KeyTitle = "title"
|
||||
KeySubtitle = "subtitle"
|
||||
KeyIcon = "icon"
|
||||
KeyPageSize = "page_size"
|
||||
KeyCookieDays = "cookie_days"
|
||||
KeyApiToken = "api_token"
|
||||
KeyOpenapiToken = "openapi_token"
|
||||
|
||||
// Security Settings Key 常量
|
||||
KeySecret = "secret"
|
||||
@@ -65,9 +66,9 @@ const (
|
||||
BindingTypeTask = "task"
|
||||
|
||||
// 系统事件类型
|
||||
EventUserLogin = "user_login"
|
||||
EventBruteForceLogin = "brute_force_login"
|
||||
EventPasswordChanged = "password_changed"
|
||||
EventUserLogin = "user_login"
|
||||
EventBruteForceLogin = "brute_force_login"
|
||||
EventPasswordChanged = "password_changed"
|
||||
|
||||
// 任务事件类型
|
||||
EventTaskSuccess = "task_success"
|
||||
|
||||
@@ -18,6 +18,18 @@ func NewExecutorController(executorService *tasks.ExecutorService) *ExecutorCont
|
||||
return &ExecutorController{executorService: executorService}
|
||||
}
|
||||
|
||||
// ExecuteTask 运行任务
|
||||
// @Summary 运行任务
|
||||
// @Description 立即执行指定的任务
|
||||
// @Tags 任务
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param id path string true "任务ID"
|
||||
// @Param body body object false "执行参数 (envs: 环境变量字典)"
|
||||
// @Success 200 {object} utils.Response{data=vo.ExecutionResultVO}
|
||||
// @Failure 400 {object} utils.Response
|
||||
// @Router /execute/task/{id} [post]
|
||||
func (ec *ExecutorController) ExecuteTask(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
@@ -42,6 +54,17 @@ func (ec *ExecutorController) ExecuteTask(c *gin.Context) {
|
||||
utils.Success(c, vo.ToExecutionResultVO(result))
|
||||
}
|
||||
|
||||
// ExecuteCommand 执行命令
|
||||
// @Summary 执行命令
|
||||
// @Description 临时执行单次命令
|
||||
// @Tags 任务执行
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param body body object true "命令内容 (需包含 command 字段)"
|
||||
// @Success 200 {object} utils.Response{data=vo.ExecutionResultVO}
|
||||
// @Failure 400 {object} utils.Response
|
||||
// @Router /execute/command [post]
|
||||
func (ec *ExecutorController) ExecuteCommand(c *gin.Context) {
|
||||
var req struct {
|
||||
Command string `json:"command" binding:"required"`
|
||||
@@ -56,6 +79,16 @@ func (ec *ExecutorController) ExecuteCommand(c *gin.Context) {
|
||||
utils.Success(c, vo.ToExecutionResultVO(result))
|
||||
}
|
||||
|
||||
// GetLastResults 获取最新执行结果
|
||||
// @Summary 获取最新执行结果
|
||||
// @Description 获取最新任务或命令执行的结果列表
|
||||
// @Tags 任务执行
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param count query int false "数量 (默认 10)"
|
||||
// @Success 200 {object} utils.Response{data=[]vo.ExecutionResultVO}
|
||||
// @Router /execute/results [get]
|
||||
func (ec *ExecutorController) GetLastResults(c *gin.Context) {
|
||||
count := 10
|
||||
if c.Query("count") != "" {
|
||||
|
||||
@@ -88,16 +88,30 @@ func (sc *SettingsController) ChangePassword(c *gin.Context) {
|
||||
// GetSiteSettings 获取站点设置
|
||||
func (sc *SettingsController) GetSiteSettings(c *gin.Context) {
|
||||
settings := sc.settingsService.GetSection(constant.SectionSite)
|
||||
|
||||
|
||||
// 解析 JSON 格式的 API Token
|
||||
if tokenJson, ok := settings[constant.KeyApiToken]; ok && tokenJson != "" {
|
||||
var tokenData map[string]string
|
||||
if err := json.Unmarshal([]byte(tokenJson), &tokenData); err == nil {
|
||||
settings["api_token"] = tokenData["token"]
|
||||
settings["api_token_expire"] = tokenData["expire_at"]
|
||||
var tokenConfig vo.TokenConfig
|
||||
if err := json.Unmarshal([]byte(tokenJson), &tokenConfig); err == nil {
|
||||
settings["api_token"] = tokenConfig.Token
|
||||
settings["api_token_expire"] = tokenConfig.ExpireAt
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 解析 JSON 格式的 OpenAPI Token
|
||||
if tokenJson, ok := settings[constant.KeyOpenapiToken]; ok && tokenJson != "" {
|
||||
var tokenConfig vo.TokenConfig
|
||||
if err := json.Unmarshal([]byte(tokenJson), &tokenConfig); err == nil {
|
||||
settings["openapi_token"] = tokenConfig.Token
|
||||
settings["openapi_token_expire"] = tokenConfig.ExpireAt
|
||||
if tokenConfig.Enabled {
|
||||
settings["openapi_enabled"] = "true"
|
||||
} else {
|
||||
settings["openapi_enabled"] = "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.Success(c, settings)
|
||||
}
|
||||
|
||||
@@ -116,13 +130,16 @@ func (sc *SettingsController) GetPublicSiteSettings(c *gin.Context) {
|
||||
// UpdateSiteSettings 更新站点设置
|
||||
func (sc *SettingsController) UpdateSiteSettings(c *gin.Context) {
|
||||
var req struct {
|
||||
Title string `json:"title"`
|
||||
Subtitle string `json:"subtitle"`
|
||||
Icon string `json:"icon"`
|
||||
PageSize string `json:"page_size"`
|
||||
CookieDays string `json:"cookie_days"`
|
||||
ApiToken string `json:"api_token"`
|
||||
ApiTokenExpire string `json:"api_token_expire"`
|
||||
Title string `json:"title"`
|
||||
Subtitle string `json:"subtitle"`
|
||||
Icon string `json:"icon"`
|
||||
PageSize string `json:"page_size"`
|
||||
CookieDays string `json:"cookie_days"`
|
||||
ApiToken string `json:"api_token"`
|
||||
ApiTokenExpire string `json:"api_token_expire"`
|
||||
OpenapiEnabled bool `json:"openapi_enabled"`
|
||||
OpenapiToken string `json:"openapi_token"`
|
||||
OpenapiTokenExpire string `json:"openapi_token_expire"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@@ -132,22 +149,35 @@ func (sc *SettingsController) UpdateSiteSettings(c *gin.Context) {
|
||||
|
||||
apiTokenJson := ""
|
||||
if req.ApiToken != "" || req.ApiTokenExpire != "" {
|
||||
tokenData := map[string]string{
|
||||
"token": req.ApiToken,
|
||||
"expire_at": req.ApiTokenExpire,
|
||||
tokenConfig := vo.TokenConfig{
|
||||
Token: req.ApiToken,
|
||||
ExpireAt: req.ApiTokenExpire,
|
||||
}
|
||||
if b, err := json.Marshal(tokenData); err == nil {
|
||||
if b, err := json.Marshal(tokenConfig); err == nil {
|
||||
apiTokenJson = string(b)
|
||||
}
|
||||
}
|
||||
|
||||
openapiTokenJson := ""
|
||||
if req.OpenapiToken != "" || req.OpenapiTokenExpire != "" || req.OpenapiEnabled {
|
||||
tokenConfig := vo.TokenConfig{
|
||||
Enabled: req.OpenapiEnabled,
|
||||
Token: req.OpenapiToken,
|
||||
ExpireAt: req.OpenapiTokenExpire,
|
||||
}
|
||||
if b, err := json.Marshal(tokenConfig); err == nil {
|
||||
openapiTokenJson = string(b)
|
||||
}
|
||||
}
|
||||
|
||||
values := map[string]string{
|
||||
constant.KeyTitle: req.Title,
|
||||
constant.KeySubtitle: req.Subtitle,
|
||||
constant.KeyIcon: req.Icon,
|
||||
constant.KeyPageSize: req.PageSize,
|
||||
constant.KeyCookieDays: req.CookieDays,
|
||||
constant.KeyApiToken: apiTokenJson,
|
||||
constant.KeyTitle: req.Title,
|
||||
constant.KeySubtitle: req.Subtitle,
|
||||
constant.KeyIcon: req.Icon,
|
||||
constant.KeyPageSize: req.PageSize,
|
||||
constant.KeyCookieDays: req.CookieDays,
|
||||
constant.KeyApiToken: apiTokenJson,
|
||||
constant.KeyOpenapiToken: openapiTokenJson,
|
||||
}
|
||||
|
||||
if err := sc.settingsService.SetSection(constant.SectionSite, values); err != nil {
|
||||
@@ -165,6 +195,13 @@ func (sc *SettingsController) GenerateApiToken(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// GenerateOpenapiToken 随机生成OpenAPI Token
|
||||
func (sc *SettingsController) GenerateOpenapiToken(c *gin.Context) {
|
||||
utils.Success(c, gin.H{
|
||||
"token": strings.ToLower(utils.RandomString(32)),
|
||||
})
|
||||
}
|
||||
|
||||
// GetSchedulerSettings 获取调度设置
|
||||
func (sc *SettingsController) GetSchedulerSettings(c *gin.Context) {
|
||||
settings := sc.settingsService.GetSection(constant.SectionScheduler)
|
||||
@@ -382,12 +419,12 @@ func (sc *SettingsController) RestoreBackup(c *gin.Context) {
|
||||
func (sc *SettingsController) GetSetting(c *gin.Context) {
|
||||
section := c.Param("section")
|
||||
key := c.Param("key")
|
||||
|
||||
|
||||
if section == "" || key == "" {
|
||||
utils.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
value := sc.settingsService.Get(section, key)
|
||||
utils.Success(c, value)
|
||||
}
|
||||
@@ -396,20 +433,20 @@ func (sc *SettingsController) GetSetting(c *gin.Context) {
|
||||
func (sc *SettingsController) GenerateSettingToken(c *gin.Context) {
|
||||
section := c.Param("section")
|
||||
key := c.Param("key")
|
||||
|
||||
|
||||
if section == "" || key == "" {
|
||||
utils.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 生成32位随机token
|
||||
token := strings.ToLower(utils.RandomString(32))
|
||||
|
||||
|
||||
// 保存到数据库
|
||||
if err := sc.settingsService.Set(section, key, token); err != nil {
|
||||
utils.ServerError(c, "保存失败")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
utils.Success(c, token)
|
||||
}
|
||||
|
||||
+98
-14
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/engigu/baihu-panel/internal/constant"
|
||||
"github.com/engigu/baihu-panel/internal/database"
|
||||
"github.com/engigu/baihu-panel/internal/models"
|
||||
"github.com/engigu/baihu-panel/internal/models/vo"
|
||||
"github.com/engigu/baihu-panel/internal/services"
|
||||
"github.com/engigu/baihu-panel/internal/utils"
|
||||
|
||||
@@ -23,6 +24,11 @@ func AuthRequired() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// 校验 OpenAPI Token
|
||||
if checkOpenapiToken(c, settingsSvc) {
|
||||
return
|
||||
}
|
||||
|
||||
token, err := c.Cookie(constant.CookieName)
|
||||
if err != nil || token == "" {
|
||||
utils.Unauthorized(c, "请先登录")
|
||||
@@ -69,20 +75,19 @@ func checkApiToken(c *gin.Context, settingsSvc *services.SettingsService) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var tokenData map[string]string
|
||||
if err := json.Unmarshal([]byte(tokenJson), &tokenData); err != nil {
|
||||
var tokenConfig vo.TokenConfig
|
||||
if err := json.Unmarshal([]byte(tokenJson), &tokenConfig); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
expectedToken, ok := tokenData["token"]
|
||||
if !ok || expectedToken == "" || apiToken != expectedToken {
|
||||
if tokenConfig.Token == "" || apiToken != tokenConfig.Token {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查过期时间
|
||||
if expireStr, ok := tokenData["expire_at"]; ok && expireStr != "" {
|
||||
if tokenConfig.ExpireAt != "" {
|
||||
// 前端传来的时间格式是 YYYY-MM-DD,使用 2006-01-02 解析
|
||||
expireDate, err := time.Parse("2006-01-02", expireStr)
|
||||
expireDate, err := time.Parse("2006-01-02", tokenConfig.ExpireAt)
|
||||
if err == nil {
|
||||
// 将过期时间设为当天的 23:59:59
|
||||
expireDate = expireDate.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
|
||||
@@ -99,7 +104,61 @@ func checkApiToken(c *gin.Context, settingsSvc *services.SettingsService) bool {
|
||||
c.Abort()
|
||||
return true // 返回 true 表示中间件已处理并截断了请求
|
||||
}
|
||||
|
||||
|
||||
c.Set("userID", adminUser.ID)
|
||||
c.Set("username", adminUser.Username)
|
||||
c.Next()
|
||||
return true
|
||||
}
|
||||
|
||||
// checkOpenapiToken 校验 OpenAPI Token
|
||||
// 返回 true 表示校验通过并已放行请求
|
||||
func checkOpenapiToken(c *gin.Context, settingsSvc *services.SettingsService) bool {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" || len(authHeader) < 8 || authHeader[:7] != "Bearer " {
|
||||
return false
|
||||
}
|
||||
openapiToken := authHeader[7:]
|
||||
|
||||
siteConfig := settingsSvc.GetSection(constant.SectionSite)
|
||||
tokenJson, ok := siteConfig[constant.KeyOpenapiToken]
|
||||
if !ok || tokenJson == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
var tokenConfig vo.TokenConfig
|
||||
if err := json.Unmarshal([]byte(tokenJson), &tokenConfig); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 校验开启状态
|
||||
if !tokenConfig.Enabled {
|
||||
return false
|
||||
}
|
||||
|
||||
if tokenConfig.Token == "" || openapiToken != tokenConfig.Token {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查过期时间
|
||||
if tokenConfig.ExpireAt != "" {
|
||||
expireDate, err := time.Parse("2006-01-02", tokenConfig.ExpireAt)
|
||||
if err == nil {
|
||||
expireDate = expireDate.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
|
||||
if time.Now().After(expireDate) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 模拟 Admin 角色
|
||||
var adminUser models.User
|
||||
if err := database.DB.Where("role = ?", "admin").First(&adminUser).Error; err != nil {
|
||||
utils.Unauthorized(c, "未找到管理员账户,OpenAPI Token 校验失败")
|
||||
c.Abort()
|
||||
return true
|
||||
}
|
||||
|
||||
c.Set("userID", adminUser.ID)
|
||||
c.Set("username", adminUser.Username)
|
||||
c.Next()
|
||||
@@ -119,22 +178,47 @@ func ClearAuthCookie(c *gin.Context) {
|
||||
|
||||
// SwaggerAuth Swagger 认证中间件 (Basic Auth)
|
||||
func SwaggerAuth() gin.HandlerFunc {
|
||||
settingsSvc := services.NewSettingsService()
|
||||
return func(c *gin.Context) {
|
||||
cfg := services.GetConfig()
|
||||
if !cfg.Swagger.Enabled {
|
||||
c.Status(404)
|
||||
siteConfig := settingsSvc.GetSection(constant.SectionSite)
|
||||
tokenJson, ok := siteConfig[constant.KeyOpenapiToken]
|
||||
if !ok || tokenJson == "" {
|
||||
c.Status(http.StatusNotFound)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
user, password, hasAuth := c.Request.BasicAuth()
|
||||
if hasAuth && user == cfg.Swagger.User && password == cfg.Swagger.Password {
|
||||
var tokenConfig vo.TokenConfig
|
||||
if err := json.Unmarshal([]byte(tokenJson), &tokenConfig); err != nil {
|
||||
c.Status(http.StatusNotFound)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 必须开启鉴权开关
|
||||
if !tokenConfig.Enabled {
|
||||
c.Status(http.StatusNotFound)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
_, password, hasAuth := c.Request.BasicAuth()
|
||||
// 允许使用任意用户名,但密码必须匹配 OpenAPI Token
|
||||
if hasAuth && password == tokenConfig.Token && tokenConfig.Token != "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("WWW-Authenticate", `Basic realm="restricted"`)
|
||||
c.Status(http.StatusUnauthorized)
|
||||
// 认证失败,提示输入密码 (如果未提供认证)
|
||||
if !hasAuth {
|
||||
c.Header("WWW-Authenticate", `Basic realm="OpenAPI Access Token (Any username)"`)
|
||||
c.Status(http.StatusUnauthorized)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 认证失败 (密码错误),返回 404 隐藏路由
|
||||
c.Status(http.StatusNotFound)
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,3 +124,10 @@ func ToLoginLogVOListFromModels(logs []models.LoginLog) []*LoginLogVO {
|
||||
}
|
||||
return vos
|
||||
}
|
||||
|
||||
// TokenConfig Token 配置结构体
|
||||
type TokenConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Token string `json:"token"`
|
||||
ExpireAt string `json:"expire_at"`
|
||||
}
|
||||
|
||||
+104
-16
@@ -1,12 +1,15 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/engigu/baihu-panel/internal/constant"
|
||||
"github.com/engigu/baihu-panel/internal/controllers"
|
||||
"github.com/engigu/baihu-panel/internal/middleware"
|
||||
"github.com/engigu/baihu-panel/internal/models/vo"
|
||||
"github.com/engigu/baihu-panel/internal/services"
|
||||
"github.com/engigu/baihu-panel/internal/static"
|
||||
|
||||
@@ -85,8 +88,79 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
})
|
||||
}
|
||||
|
||||
// Swagger documentation (带 Basic Auth 认证)
|
||||
root.GET("/swagger/*any", middleware.SwaggerAuth(), ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
// OpenAPI documentation using Scalar UI (带 Basic Auth 认证)
|
||||
router.GET("/openapi/*any", func(c *gin.Context) {
|
||||
settingsSvc := services.NewSettingsService()
|
||||
siteConfig := settingsSvc.GetSection(constant.SectionSite)
|
||||
tokenJson := siteConfig[constant.KeyOpenapiToken]
|
||||
|
||||
enabled := false
|
||||
if tokenJson != "" {
|
||||
var tokenConfig vo.TokenConfig
|
||||
if err := json.Unmarshal([]byte(tokenJson), &tokenConfig); err == nil {
|
||||
enabled = tokenConfig.Enabled
|
||||
}
|
||||
}
|
||||
|
||||
// 如果未开启文档,直接返回 404 SPA 页面
|
||||
if !enabled {
|
||||
serveSPA(c, urlPrefix, 404)
|
||||
return
|
||||
}
|
||||
|
||||
// 执行认证
|
||||
middleware.SwaggerAuth()(c)
|
||||
if c.IsAborted() {
|
||||
// 如果认证失败(且被中间件置为 404,如密码错误且我们想要隐藏它)
|
||||
if c.Writer.Status() == 404 {
|
||||
serveSPA(c, urlPrefix, 404)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 获取内部路径(移除开头的斜杠)
|
||||
path := strings.TrimPrefix(c.Param("any"), "/")
|
||||
|
||||
// 1. 根路径或空路径 -> 重定向到 index.html
|
||||
if path == "" || path == "/" {
|
||||
c.Redirect(http.StatusMovedPermanently, urlPrefix+"/openapi/index.html")
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 提供 Scalar 渲染的 HTML 页面
|
||||
if path == "index.html" {
|
||||
scalarHTML := `<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Baihu Panel API Reference</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script id="api-reference" data-url="` + urlPrefix + `/openapi/doc.json"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
||||
</body>
|
||||
</html>`
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.String(http.StatusOK, scalarHTML)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 提供给 Scalar/Swagger 使用的 doc.json 内容
|
||||
if path == "doc.json" {
|
||||
// 这里借助 ginSwagger 仅生成 doc.json 内容
|
||||
h := ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL(urlPrefix+"/openapi/doc.json"))
|
||||
h(c)
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 其余路径一律返回 SPA 的 404
|
||||
serveSPA(c, urlPrefix, 404)
|
||||
})
|
||||
|
||||
// API 路由组
|
||||
api := root.Group("/api/v1")
|
||||
@@ -199,6 +273,7 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
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)
|
||||
@@ -314,21 +389,34 @@ func Setup(c *Controllers) *gin.Engine {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := static.ReadFile("index.html")
|
||||
if err != nil {
|
||||
ctx.Status(404)
|
||||
return
|
||||
}
|
||||
|
||||
html := string(data)
|
||||
|
||||
// 注入配置变量供前端使用(API 调用和路由)
|
||||
configScript := `<script>window.__BASE_URL__ = "` + urlPrefix + `"; window.__API_VERSION__ = "/api/v1";</script>`
|
||||
html = strings.Replace(html, "</head>", configScript+"</head>", 1)
|
||||
|
||||
ctx.Header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
ctx.Data(200, "text/html; charset=utf-8", []byte(html))
|
||||
serveSPA(ctx, urlPrefix, 200)
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// serveSPA 注入配置并返回 index.html 给前端渲染
|
||||
func serveSPA(ctx *gin.Context, urlPrefix string, status int) {
|
||||
data, err := static.ReadFile("index.html")
|
||||
if err != nil {
|
||||
// 如果读不到 index.html (如 dev 模式未 build),返回基础 HTML 触发前端路由
|
||||
fallback := `<!DOCTYPE html><html><head><meta charset="utf-8"/><title>404 Not Found</title></head><body>
|
||||
<script>window.location.href = (window.__BASE_URL__ || "/") + "404";</script>
|
||||
<p>Not Found. Redirecting to home...</p>
|
||||
</body></html>`
|
||||
ctx.Header("Content-Type", "text/html; charset=utf-8")
|
||||
ctx.Data(status, "text/html", []byte(fallback))
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
html := string(data)
|
||||
|
||||
// 注入配置变量供前端使用(API 调用和路由)
|
||||
configScript := `<script>window.__BASE_URL__ = "` + urlPrefix + `"; window.__API_VERSION__ = "/api/v1";</script>`
|
||||
html = strings.Replace(html, "</head>", configScript+"</head>", 1)
|
||||
|
||||
ctx.Header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
ctx.Data(status, "text/html; charset=utf-8", []byte(html))
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
@@ -31,17 +31,10 @@ type SecurityConfig struct {
|
||||
Secret string `ini:"secret"`
|
||||
}
|
||||
|
||||
type SwaggerConfig struct {
|
||||
Enabled bool `ini:"enabled"`
|
||||
User string `ini:"user"`
|
||||
Password string `ini:"password"`
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Server ServerConfig `ini:"server"`
|
||||
Database DatabaseConfig `ini:"database"`
|
||||
Security SecurityConfig `ini:"security"`
|
||||
Swagger SwaggerConfig `ini:"swagger"`
|
||||
}
|
||||
|
||||
var Config *AppConfig
|
||||
@@ -82,11 +75,6 @@ func LoadConfig(path string) (*AppConfig, error) {
|
||||
Security: SecurityConfig{
|
||||
Secret: "",
|
||||
},
|
||||
Swagger: SwaggerConfig{
|
||||
Enabled: false,
|
||||
User: "admin",
|
||||
Password: "swagger_password",
|
||||
},
|
||||
}
|
||||
|
||||
// 检查配置文件是否存在
|
||||
@@ -154,15 +142,6 @@ func applyEnvOverrides() {
|
||||
// Security
|
||||
getEnvStr("BH_SECRET", &Config.Security.Secret)
|
||||
|
||||
// Swagger
|
||||
vSwaggerEnabled := os.Getenv("BH_SWAGGER_ENABLED")
|
||||
if vSwaggerEnabled == "true" || vSwaggerEnabled == "1" {
|
||||
Config.Swagger.Enabled = true
|
||||
} else if vSwaggerEnabled == "false" || vSwaggerEnabled == "0" {
|
||||
Config.Swagger.Enabled = false
|
||||
}
|
||||
getEnvStr("BH_SWAGGER_USER", &Config.Swagger.User)
|
||||
getEnvStr("BH_SWAGGER_PASSWORD", &Config.Swagger.Password)
|
||||
}
|
||||
|
||||
func GetConfig() *AppConfig {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/engigu/baihu-panel/cmd"
|
||||
"github.com/engigu/baihu-panel/internal/bootstrap"
|
||||
"github.com/engigu/baihu-panel/internal/constant"
|
||||
_ "github.com/engigu/baihu-panel/docs" // This will be generated by swag init
|
||||
_ "github.com/engigu/baihu-panel/openapi_docs" // This will be generated by swag init
|
||||
)
|
||||
|
||||
// @title Baihu Panel API
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||
package docs
|
||||
// Package openapi_docs Code generated by swaggo/swag. DO NOT EDIT
|
||||
package openapi_docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
|
||||
@@ -68,7 +68,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -132,7 +132,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -148,7 +148,7 @@ const docTemplate = `{
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -230,7 +230,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -246,7 +246,7 @@ const docTemplate = `{
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,7 +292,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -308,7 +308,7 @@ const docTemplate = `{
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,7 +349,7 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
@@ -357,7 +357,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -409,7 +409,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -428,6 +428,177 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/execute/command": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "临时执行单次命令",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"任务执行"
|
||||
],
|
||||
"summary": "执行命令",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "命令内容 (需包含 command 字段)",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/vo.ExecutionResultVO"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/execute/results": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取最新任务或命令执行的结果列表",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"任务执行"
|
||||
],
|
||||
"summary": "获取最新执行结果",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "数量 (默认 10)",
|
||||
"name": "count",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/vo.ExecutionResultVO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/execute/task/{id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "立即执行指定的任务",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"任务"
|
||||
],
|
||||
"summary": "运行任务",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "任务ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "执行参数 (envs: 环境变量字典)",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/vo.ExecutionResultVO"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/logs": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -484,7 +655,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -547,13 +718,13 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,7 +763,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -608,7 +779,7 @@ const docTemplate = `{
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -643,13 +814,13 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -717,7 +888,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -779,13 +950,13 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -824,7 +995,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -840,7 +1011,7 @@ const docTemplate = `{
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -886,7 +1057,7 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -902,7 +1073,7 @@ const docTemplate = `{
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -937,13 +1108,13 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -951,18 +1122,6 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"github_com_engigu_baihu-panel_internal_utils.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"data": {},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"utils.PaginationData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -978,6 +1137,18 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"utils.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"data": {},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vo.EnvVO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1004,6 +1175,41 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"vo.ExecutionResultVO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "integer"
|
||||
},
|
||||
"end_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"exit_code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"log_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"output": {
|
||||
"type": "string"
|
||||
},
|
||||
"start_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"task_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vo.TaskLogVO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -62,7 +62,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -126,7 +126,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -142,7 +142,7 @@
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,7 +172,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -224,7 +224,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -240,7 +240,7 @@
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,7 +286,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -302,7 +302,7 @@
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,7 +343,7 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
@@ -351,7 +351,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -403,7 +403,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -422,6 +422,177 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/execute/command": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "临时执行单次命令",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"任务执行"
|
||||
],
|
||||
"summary": "执行命令",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "命令内容 (需包含 command 字段)",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/vo.ExecutionResultVO"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/execute/results": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "获取最新任务或命令执行的结果列表",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"任务执行"
|
||||
],
|
||||
"summary": "获取最新执行结果",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "数量 (默认 10)",
|
||||
"name": "count",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/vo.ExecutionResultVO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/execute/task/{id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "立即执行指定的任务",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"任务"
|
||||
],
|
||||
"summary": "运行任务",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "任务ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "执行参数 (envs: 环境变量字典)",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/vo.ExecutionResultVO"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/logs": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -478,7 +649,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -541,13 +712,13 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -586,7 +757,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -602,7 +773,7 @@
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -637,13 +808,13 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -711,7 +882,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -773,13 +944,13 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -818,7 +989,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -834,7 +1005,7 @@
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -880,7 +1051,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -896,7 +1067,7 @@
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -931,13 +1102,13 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_engigu_baihu-panel_internal_utils.Response"
|
||||
"$ref": "#/definitions/utils.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -945,18 +1116,6 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"github_com_engigu_baihu-panel_internal_utils.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"data": {},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"utils.PaginationData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -972,6 +1131,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"utils.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"data": {},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vo.EnvVO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -998,6 +1169,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vo.ExecutionResultVO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "integer"
|
||||
},
|
||||
"end_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"exit_code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"log_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"output": {
|
||||
"type": "string"
|
||||
},
|
||||
"start_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"task_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vo.TaskLogVO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1,13 +1,5 @@
|
||||
basePath: /api/v1
|
||||
definitions:
|
||||
github_com_engigu_baihu-panel_internal_utils.Response:
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
data: {}
|
||||
msg:
|
||||
type: string
|
||||
type: object
|
||||
utils.PaginationData:
|
||||
properties:
|
||||
list: {}
|
||||
@@ -18,6 +10,14 @@ definitions:
|
||||
total:
|
||||
type: integer
|
||||
type: object
|
||||
utils.Response:
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
data: {}
|
||||
msg:
|
||||
type: string
|
||||
type: object
|
||||
vo.EnvVO:
|
||||
properties:
|
||||
created_at:
|
||||
@@ -35,6 +35,29 @@ definitions:
|
||||
value:
|
||||
type: string
|
||||
type: object
|
||||
vo.ExecutionResultVO:
|
||||
properties:
|
||||
duration:
|
||||
type: integer
|
||||
end_time:
|
||||
type: string
|
||||
error:
|
||||
type: string
|
||||
exit_code:
|
||||
type: integer
|
||||
log_id:
|
||||
type: string
|
||||
output:
|
||||
type: string
|
||||
start_time:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
task_id:
|
||||
type: string
|
||||
type: object
|
||||
vo.TaskLogVO:
|
||||
properties:
|
||||
agent_id:
|
||||
@@ -156,7 +179,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
allOf:
|
||||
@@ -191,7 +214,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/vo.EnvVO'
|
||||
@@ -199,7 +222,7 @@ paths:
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 创建环境变量
|
||||
@@ -226,12 +249,12 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
"409":
|
||||
description: 引用冲突
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
@@ -260,7 +283,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/vo.EnvVO'
|
||||
@@ -268,7 +291,7 @@ paths:
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 获取环境变量详情
|
||||
@@ -297,7 +320,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/vo.EnvVO'
|
||||
@@ -305,7 +328,7 @@ paths:
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 更新环境变量
|
||||
@@ -329,7 +352,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
@@ -353,7 +376,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
@@ -365,6 +388,105 @@ paths:
|
||||
summary: 获取所有环境变量
|
||||
tags:
|
||||
- 环境变量
|
||||
/execute/command:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 临时执行单次命令
|
||||
parameters:
|
||||
- description: 命令内容 (需包含 command 字段)
|
||||
in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/vo.ExecutionResultVO'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 执行命令
|
||||
tags:
|
||||
- 任务执行
|
||||
/execute/results:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 获取最新任务或命令执行的结果列表
|
||||
parameters:
|
||||
- description: 数量 (默认 10)
|
||||
in: query
|
||||
name: count
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/vo.ExecutionResultVO'
|
||||
type: array
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 获取最新执行结果
|
||||
tags:
|
||||
- 任务执行
|
||||
/execute/task/{id}:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 立即执行指定的任务
|
||||
parameters:
|
||||
- description: 任务ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: '执行参数 (envs: 环境变量字典)'
|
||||
in: body
|
||||
name: body
|
||||
schema:
|
||||
type: object
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/vo.ExecutionResultVO'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 运行任务
|
||||
tags:
|
||||
- 任务
|
||||
/logs:
|
||||
get:
|
||||
consumes:
|
||||
@@ -398,7 +520,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
allOf:
|
||||
@@ -432,11 +554,11 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 删除日志
|
||||
@@ -459,7 +581,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/vo.TaskLogVO'
|
||||
@@ -467,7 +589,7 @@ paths:
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 获取日志详情
|
||||
@@ -490,11 +612,11 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 清空日志
|
||||
@@ -537,7 +659,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
allOf:
|
||||
@@ -571,11 +693,11 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 删除任务
|
||||
@@ -598,7 +720,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/vo.TaskVO'
|
||||
@@ -606,7 +728,7 @@ paths:
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 获取任务详情
|
||||
@@ -635,7 +757,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
- $ref: '#/definitions/utils.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/vo.TaskVO'
|
||||
@@ -643,7 +765,7 @@ paths:
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 更新任务
|
||||
@@ -666,11 +788,11 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_engigu_baihu-panel_internal_utils.Response'
|
||||
$ref: '#/definitions/utils.Response'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 停止任务
|
||||
@@ -133,6 +133,7 @@ export const api = {
|
||||
updateSite: (data: SiteSettings) =>
|
||||
request('/settings/site', { method: 'PUT', body: JSON.stringify(data) }),
|
||||
generateApiToken: () => request<{ token: string }>('/settings/site/api-token/generate', { method: 'POST' }),
|
||||
generateOpenapiToken: () => request<{ token: string }>('/settings/site/openapi-token/generate', { method: 'POST' }),
|
||||
getScheduler: () => request<SchedulerSettings>('/settings/scheduler'),
|
||||
updateScheduler: (data: SchedulerSettings) =>
|
||||
request('/settings/scheduler', { method: 'PUT', body: JSON.stringify(data) }),
|
||||
@@ -431,6 +432,9 @@ export interface SiteSettings {
|
||||
icon: string
|
||||
page_size: string
|
||||
cookie_days: string
|
||||
openapi_enabled?: boolean
|
||||
openapi_token?: string
|
||||
openapi_token_expire?: string
|
||||
api_token?: string
|
||||
api_token_expire?: string
|
||||
}
|
||||
|
||||
@@ -50,6 +50,15 @@ const router = createRouter({
|
||||
{ path: 'notify', name: 'notify', component: () => import('@/views/notify/Notify.vue') },
|
||||
{ path: 'settings', name: 'settings', component: () => import('@/views/settings/Settings.vue') }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
name: '404',
|
||||
component: () => import('@/views/error/NotFound.vue')
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: '/404'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Home } from 'lucide-vue-next'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
function goHome() {
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fixed inset-0 flex items-center justify-center p-6 sm:p-12 overflow-hidden bg-background">
|
||||
<!-- Subtle Background Decoration -->
|
||||
<div class="absolute inset-0 z-0 pointer-events-none overflow-hidden">
|
||||
<div
|
||||
class="absolute -top-[10%] -left-[5%] w-[40%] h-[40%] rounded-full bg-primary/5 blur-[120px] animate-pulse" />
|
||||
<div
|
||||
class="absolute -bottom-[10%] -right-[5%] w-[40%] h-[40%] rounded-full bg-blue-500/5 blur-[120px] delay-1000 animate-pulse" />
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full h-full opacity-[0.03] dark:opacity-[0.05]"
|
||||
style="background-image: radial-gradient(circle at 2px 2px, currentColor 1px, transparent 0); background-size: 40px 40px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 max-w-lg w-full text-center space-y-8 animate-in fade-in zoom-in-95 duration-700">
|
||||
<!-- 404 Illustration/Text -->
|
||||
<div class="relative inline-block mt-4">
|
||||
<h1
|
||||
class="text-[12rem] sm:text-[15rem] font-black leading-none tracking-tighter text-transparent bg-clip-text bg-gradient-to-b from-primary via-primary/80 to-transparent opacity-10 select-none">
|
||||
404
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Text Content -->
|
||||
<div class="space-y-4 px-4">
|
||||
<h2 class="text-3xl sm:text-4xl font-extrabold tracking-tight text-foreground">看起来迷路了</h2>
|
||||
<p class="text-lg text-muted-foreground max-w-sm mx-auto leading-relaxed">
|
||||
抱歉,您访问的页面不存在或已被移除。您可以尝试返回上一页或回到首页继续。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex justify-center pt-4">
|
||||
<Button variant="default" size="lg" @click="goHome"
|
||||
class="w-full sm:w-auto px-12 gap-2 active:scale-95 transition-all shadow-lg shadow-primary/20">
|
||||
<Home class="w-4 h-4" />
|
||||
返回首页
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Footer Help -->
|
||||
<div class="pt-12 text-sm text-zinc-400 dark:text-zinc-600 animate-in slide-in-from-bottom-4 duration-1000">
|
||||
<p>如果有疑问,请联系系统管理员</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@keyframes pulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 8s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,17 @@ import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Copy, Terminal, Key, FileJson, RefreshCw, Check, Hash, Info } from 'lucide-vue-next'
|
||||
import { Copy, Terminal, Key, FileJson, RefreshCw, Check, Hash, Info, AlertTriangle } from 'lucide-vue-next'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import type { NotifyChannel, ChannelType } from '@/api'
|
||||
import { ref, computed } from 'vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
@@ -22,6 +32,20 @@ const emit = defineEmits<{
|
||||
|
||||
const copiedBlock = ref<string | null>(null)
|
||||
const host = ref(window.location.host)
|
||||
const showConfirmDialog = ref(false)
|
||||
|
||||
function onGenerateClick() {
|
||||
if (props.apiToken) {
|
||||
showConfirmDialog.value = true
|
||||
} else {
|
||||
emit('generateToken')
|
||||
}
|
||||
}
|
||||
|
||||
function handleConfirmGenerate() {
|
||||
showConfirmDialog.value = false
|
||||
emit('generateToken')
|
||||
}
|
||||
|
||||
function copyToClipboard(text: string, blockId: string) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
@@ -74,8 +98,7 @@ const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v
|
||||
<Copy v-else class="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="default" @click="emit('generateToken')"
|
||||
class="h-10 px-4 shrink-0 transition-all active:scale-95">
|
||||
<Button variant="default" @click="onGenerateClick" class="h-10 px-4 shrink-0 transition-all active:scale-95">
|
||||
<RefreshCw class="w-3.5 h-3.5 mr-2" />
|
||||
{{ apiToken ? '重新生成' : '生成 Token' }}
|
||||
</Button>
|
||||
@@ -191,7 +214,7 @@ const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v
|
||||
<div class="space-y-1">
|
||||
<p><span class="text-zinc-500"># 使用 CURL 调用推送接口</span></p>
|
||||
<p>curl -s -X POST <span class="text-emerald-600 dark:text-emerald-400">"http://{{ host
|
||||
}}/api/v1/notify/send"</span> \</p>
|
||||
}}/api/v1/notify/send"</span> \</p>
|
||||
<p class="pl-4"> -H <span class="text-orange-600 dark:text-orange-400">"Content-Type:
|
||||
application/json"</span> \</p>
|
||||
<p class="pl-4"> -H <span class="text-orange-600 dark:text-orange-400">"notify-token: {{ apiToken ||
|
||||
@@ -227,6 +250,25 @@ const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 重新生成确认弹窗 -->
|
||||
<AlertDialog :open="showConfirmDialog" @update:open="showConfirmDialog = $event">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle class="flex items-center gap-2">
|
||||
<AlertTriangle class="w-5 h-5 text-amber-500" />
|
||||
确认重新生成 Token?
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
此操作将立刻覆盖当前 Token,旧的 Token 将会永久失效。确认要继续吗?
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction @click="handleConfirmGenerate">确认重新生成</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -7,20 +7,38 @@ import { api, type SiteSettings } from '@/api'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { RefreshCw, Copy } from 'lucide-vue-next'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { RefreshCw, Copy, AlertTriangle, ExternalLink } from 'lucide-vue-next'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog'
|
||||
|
||||
const { refreshSettings } = useSiteSettings()
|
||||
|
||||
const baseUrl = (window as any).__BASE_URL__ || ''
|
||||
|
||||
const form = ref<SiteSettings>({
|
||||
title: '',
|
||||
subtitle: '',
|
||||
icon: '',
|
||||
page_size: '10',
|
||||
cookie_days: '7',
|
||||
openapi_enabled: false,
|
||||
openapi_token: '',
|
||||
openapi_token_expire: '',
|
||||
api_token: '',
|
||||
api_token_expire: ''
|
||||
})
|
||||
const loading = ref(false)
|
||||
const showOpenapiConfirmDialog = ref(false)
|
||||
const showApiConfirmDialog = ref(false)
|
||||
|
||||
const iconPreview = computed(() => {
|
||||
if (!form.value.icon) return ''
|
||||
@@ -34,8 +52,11 @@ const iconPreview = computed(() => {
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const res = await api.settings.getSite()
|
||||
form.value = res
|
||||
} catch {}
|
||||
form.value = {
|
||||
...res,
|
||||
openapi_enabled: res.openapi_enabled === true || (res as any).openapi_enabled === 'true'
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
@@ -55,11 +76,27 @@ async function saveSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
async function generateOpenapiToken() {
|
||||
try {
|
||||
const res = await api.settings.generateOpenapiToken()
|
||||
form.value.openapi_token = res.token
|
||||
|
||||
// 如果没有设置过期时间,默认给一年后
|
||||
if (!form.value.openapi_token_expire) {
|
||||
const d = new Date()
|
||||
d.setFullYear(d.getFullYear() + 1)
|
||||
form.value.openapi_token_expire = d.toISOString().split('T')[0]
|
||||
}
|
||||
} catch {
|
||||
toast.error('生成 Token 失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function generateToken() {
|
||||
try {
|
||||
const res = await api.settings.generateApiToken()
|
||||
form.value.api_token = res.token
|
||||
|
||||
|
||||
// 如果没有设置过期时间,默认给一年后
|
||||
if (!form.value.api_token_expire) {
|
||||
const d = new Date()
|
||||
@@ -71,6 +108,16 @@ async function generateToken() {
|
||||
}
|
||||
}
|
||||
|
||||
async function copyOpenapiToken() {
|
||||
if (!form.value.openapi_token) return
|
||||
try {
|
||||
await navigator.clipboard.writeText(form.value.openapi_token)
|
||||
toast.success('Token 已复制到剪贴板')
|
||||
} catch {
|
||||
toast.error('复制失败,请手动复制')
|
||||
}
|
||||
}
|
||||
|
||||
async function copyToken() {
|
||||
if (!form.value.api_token) return
|
||||
try {
|
||||
@@ -98,7 +145,9 @@ onMounted(loadSettings)
|
||||
<Label class="sm:text-right">站点图标</Label>
|
||||
<div class="sm:col-span-3 flex items-center gap-2">
|
||||
<Input v-model="form.icon" placeholder="<svg>...</svg>" class="flex-1 font-mono text-xs" />
|
||||
<div v-if="iconPreview" class="p-1.5 border rounded bg-white dark:bg-white w-8 h-8 flex items-center justify-center shrink-0 [&>svg]:w-5 [&>svg]:h-5" v-html="iconPreview" />
|
||||
<div v-if="iconPreview"
|
||||
class="p-1.5 border rounded bg-white dark:bg-white w-8 h-8 flex items-center justify-center shrink-0 [&>svg]:w-5 [&>svg]:h-5"
|
||||
v-html="iconPreview" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4">
|
||||
@@ -114,19 +163,69 @@ onMounted(loadSettings)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="pt-6 border-t mt-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-lg font-medium text-foreground">OpenAPI Token</h3>
|
||||
<Badge variant="secondary"
|
||||
class="font-normal text-xs bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20">推荐方式</Badge>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<a :href="baseUrl + '/openapi/index.html'" target="_blank"
|
||||
class="flex items-center gap-1 text-xs text-blue-600 hover:underline">
|
||||
查看接口文档
|
||||
<ExternalLink class="w-3 h-3" />
|
||||
</a>
|
||||
<div class="flex items-center gap-2">
|
||||
<Switch v-model="form.openapi_enabled" id="openapi-enabled" />
|
||||
<Label for="openapi-enabled" class="text-xs cursor-pointer">开启鉴权</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground mb-4">开启全局 OpenAPI 直接访问能力,配置后可通过请求头 <code
|
||||
class="bg-muted px-1.5 py-0.5 rounded text-xs select-all font-sans">Authorization: Bearer <在此生成的Token></code>
|
||||
无需登录直接调用系统的所有接口,请妥善保管并设置合理的有效期。</p>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4 mb-4">
|
||||
<Label class="sm:text-right text-muted-foreground">Token 密钥</Label>
|
||||
<div class="sm:col-span-3 flex w-full max-w-sm items-center space-x-2">
|
||||
<Input v-model="form.openapi_token" placeholder="点击右侧按钮生成 32 位随机 Token" class="text-sm" />
|
||||
<Button type="button" variant="outline" size="icon" @click="showOpenapiConfirmDialog = true" title="随机生成">
|
||||
<RefreshCw class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="icon" @click="copyOpenapiToken" title="复制"
|
||||
:disabled="!form.openapi_token">
|
||||
<Copy class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4">
|
||||
<Label class="sm:text-right text-muted-foreground">截止有效期</Label>
|
||||
<div class="sm:col-span-3">
|
||||
<Input v-model="form.openapi_token_expire" type="date" class="w-full max-w-xs dark:[color-scheme:dark]" />
|
||||
<div class="text-xs text-muted-foreground mt-1.5 ml-1">超过此日期后该 Token 将失效,置空代表该特性完全关闭。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-6 border-t mt-6">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<h3 class="text-lg font-medium text-foreground">API Token</h3>
|
||||
<Badge variant="secondary" class="font-normal text-xs bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20">实验特性,可能变更</Badge>
|
||||
<Badge variant="secondary"
|
||||
class="font-normal text-xs bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20">
|
||||
实验特性,可能变更,将会在后期下线</Badge>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground mb-4">开启全局 API 直接访问能力,配置后可通过请求头 <code class="bg-muted px-1.5 py-0.5 rounded text-xs select-all font-sans">X-API-Token: <在此生成的Token></code> 无需登录直接调用系统的所有接口,请妥善保管并设置合理的有效期。</p>
|
||||
|
||||
<p class="text-sm text-muted-foreground mb-4">开启全局 API 直接访问能力,配置后可通过请求头 <code
|
||||
class="bg-muted px-1.5 py-0.5 rounded text-xs select-all font-sans">X-API-Token: <在此生成的Token></code>
|
||||
无需登录直接调用系统的所有接口,请妥善保管并设置合理的有效期。</p>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-4 mb-4">
|
||||
<Label class="sm:text-right text-muted-foreground">Token 密钥</Label>
|
||||
<div class="sm:col-span-3 flex w-full max-w-sm items-center space-x-2">
|
||||
<Input v-model="form.api_token" placeholder="点击右侧按钮生成 32 位随机 Token" class="text-sm" />
|
||||
<Button type="button" variant="outline" size="icon" @click="generateToken" title="随机生成">
|
||||
<Button type="button" variant="outline" size="icon" @click="showApiConfirmDialog = true" title="随机生成">
|
||||
<RefreshCw class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="icon" @click="copyToken" title="复制" :disabled="!form.api_token">
|
||||
@@ -148,5 +247,43 @@ onMounted(loadSettings)
|
||||
{{ loading ? '保存中...' : '保存设置' }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- OpenAPI Token 重新生成确认弹窗 -->
|
||||
<AlertDialog :open="showOpenapiConfirmDialog" @update:open="showOpenapiConfirmDialog = $event">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle class="flex items-center gap-2">
|
||||
<AlertTriangle class="w-5 h-5 text-amber-500" />
|
||||
确认重新生成 Token?
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
此操作将立刻覆盖当前配置框内的 OpenAPI Token,原有的 Token 在点击【保存设置】后将会永久失效,导致所有使用旧 Token 的外部系统无法访问。确认要继续吗?
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction @click="generateOpenapiToken">重新生成</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<!-- API Token 重新生成确认弹窗 -->
|
||||
<AlertDialog :open="showApiConfirmDialog" @update:open="showApiConfirmDialog = $event">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle class="flex items-center gap-2">
|
||||
<AlertTriangle class="w-5 h-5 text-amber-500" />
|
||||
确认重新生成 Token?
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
此操作将立刻覆盖当前配置框内的旧版 API Token,原有的 Token 在点击【保存设置】后将会永久失效。建议逐步迁移到 OpenAPI Token 后直接将此特性置空关闭。确认要继续吗?
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction @click="generateToken">重新生成</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -16,6 +16,10 @@ export default defineConfig({
|
||||
target: 'http://localhost:8052',
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
},
|
||||
'/openapi': {
|
||||
target: 'http://localhost:8052',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user