diff --git a/Dockerfile b/Dockerfile index 586d5c1..e4b7a8e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -58,6 +58,7 @@ RUN sed -i 's@deb.debian.org@mirrors.tuna.tsinghua.edu.cn@g' /etc/apt/sources.li && "${CONDA_DIR}"/bin/conda config --set show_channel_urls yes \ && "${CONDA_DIR}"/bin/conda config --set channel_priority strict \ && "${CONDA_DIR}"/bin/conda init \ + && "${CONDA_DIR}"/bin/python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple \ && "${CONDA_DIR}"/bin/conda clean -afy WORKDIR /app diff --git a/internal/controllers/env_controller.go b/internal/controllers/env_controller.go index 57a53df..2bc6b12 100644 --- a/internal/controllers/env_controller.go +++ b/internal/controllers/env_controller.go @@ -43,6 +43,12 @@ func (ec *EnvController) GetEnvVars(c *gin.Context) { utils.PaginatedResponse(c, envVars, total, p) } +func (ec *EnvController) GetAllEnvVars(c *gin.Context) { + userID := 1 + envVars := ec.envService.GetEnvVarsByUserID(userID) + utils.Success(c, envVars) +} + func (ec *EnvController) GetEnvVar(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { diff --git a/internal/controllers/settings_controller.go b/internal/controllers/settings_controller.go index ef25be0..603a36a 100644 --- a/internal/controllers/settings_controller.go +++ b/internal/controllers/settings_controller.go @@ -62,24 +62,7 @@ func (sc *SettingsController) ChangePassword(c *gin.Context) { utils.SuccessMsg(c, "密码修改成功") } -// CleanLogs 清理日志 -func (sc *SettingsController) CleanLogs(c *gin.Context) { - var req struct { - Days int `json:"days" binding:"required,min=1"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - utils.BadRequest(c, "参数错误") - return - } - - cutoff := time.Now().AddDate(0, 0, -req.Days) - result := database.DB.Where("created_at < ?", cutoff).Delete(&models.TaskLog{}) - - utils.Success(c, gin.H{ - "deleted": result.RowsAffected, - }) -} +// CleanLogs 清理日志 - 已移除,改为任务级别的日志清理配置 // GetSiteSettings 获取站点设置 func (sc *SettingsController) GetSiteSettings(c *gin.Context) { diff --git a/internal/controllers/task_controller.go b/internal/controllers/task_controller.go index 8fa9e8b..5d35ccb 100644 --- a/internal/controllers/task_controller.go +++ b/internal/controllers/task_controller.go @@ -28,6 +28,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) { Schedule string `json:"schedule" binding:"required"` Timeout int `json:"timeout"` CleanConfig string `json:"clean_config"` + Envs string `json:"envs"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -40,7 +41,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) { return } - task := tc.taskService.CreateTask(req.Name, req.Command, req.Schedule, req.Timeout, req.CleanConfig) + task := tc.taskService.CreateTask(req.Name, req.Command, req.Schedule, req.Timeout, req.CleanConfig, req.Envs) tc.cronService.AddTask(task) utils.Success(c, task) @@ -83,6 +84,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { Schedule string `json:"schedule"` Timeout int `json:"timeout"` CleanConfig string `json:"clean_config"` + Envs string `json:"envs"` Enabled bool `json:"enabled"` } @@ -98,7 +100,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { } } - task := tc.taskService.UpdateTask(id, req.Name, req.Command, req.Schedule, req.Timeout, req.CleanConfig, req.Enabled) + task := tc.taskService.UpdateTask(id, req.Name, req.Command, req.Schedule, req.Timeout, req.CleanConfig, req.Envs, req.Enabled) if task == nil { utils.NotFound(c, "任务不存在") return diff --git a/internal/models/task.go b/internal/models/task.go index f7156ad..08f888c 100644 --- a/internal/models/task.go +++ b/internal/models/task.go @@ -20,6 +20,7 @@ type Task struct { Schedule string `json:"schedule" gorm:"size:100"` // cron expression Timeout int `json:"timeout" gorm:"default:30"` // 超时时间(分钟),默认30分钟 CleanConfig string `json:"clean_config" gorm:"size:255;default:''"` // 清理配置 JSON + Envs string `json:"envs" gorm:"size:255;default:''"` // 环境变量ID列表,逗号分隔 Enabled bool `json:"enabled" gorm:"default:true"` LastRun *LocalTime `json:"last_run"` NextRun *LocalTime `json:"next_run"` diff --git a/internal/router/router.go b/internal/router/router.go index dd93731..522b909 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -128,6 +128,7 @@ func Setup(c *Controllers) *gin.Engine { { env.POST("", c.Env.CreateEnvVar) env.GET("", c.Env.GetEnvVars) + env.GET("/all", c.Env.GetAllEnvVars) env.GET("/:id", c.Env.GetEnvVar) env.PUT("/:id", c.Env.UpdateEnvVar) env.DELETE("/:id", c.Env.DeleteEnvVar) @@ -171,7 +172,6 @@ func Setup(c *Controllers) *gin.Engine { settings := authorized.Group("/settings") { settings.POST("/password", c.Settings.ChangePassword) - settings.POST("/cleanlogs", c.Settings.CleanLogs) settings.GET("/site", c.Settings.GetSiteSettings) settings.PUT("/site", c.Settings.UpdateSiteSettings) settings.GET("/about", c.Settings.GetAbout) diff --git a/internal/services/env_service.go b/internal/services/env_service.go index c8305fa..a218756 100644 --- a/internal/services/env_service.go +++ b/internal/services/env_service.go @@ -1,6 +1,9 @@ package services import ( + "strconv" + "strings" + "baihu/internal/database" "baihu/internal/models" ) @@ -66,3 +69,32 @@ func (es *EnvService) DeleteEnvVar(id int) bool { result := database.DB.Delete(&models.EnvironmentVariable{}, id) return result.RowsAffected > 0 } + +// GetEnvVarsByIDs 根据逗号分隔的ID字符串获取环境变量列表,返回 NAME=VALUE 格式 +func (es *EnvService) GetEnvVarsByIDs(envIDs string) []string { + if envIDs == "" { + return nil + } + + var envVars []string + ids := splitEnvIDs(envIDs) + for _, id := range ids { + env := es.GetEnvVarByID(id) + if env != nil { + envVars = append(envVars, env.Name+"="+env.Value) + } + } + return envVars +} + +// splitEnvIDs 解析逗号分隔的ID字符串 +func splitEnvIDs(envIDs string) []int { + var ids []int + for _, s := range strings.Split(envIDs, ",") { + s = strings.TrimSpace(s) + if id, err := strconv.Atoi(s); err == nil { + ids = append(ids, id) + } + } + return ids +} diff --git a/internal/services/executor_service.go b/internal/services/executor_service.go index ea61824..346ab40 100644 --- a/internal/services/executor_service.go +++ b/internal/services/executor_service.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "os" "os/exec" "sync" "time" @@ -179,12 +180,16 @@ func (es *ExecutorService) ExecuteTask(taskID int) *ExecutionResult { es.runningTasks[taskID] = true es.mu.Unlock() + // 加载环境变量 + envService := NewEnvService() + envVars := envService.GetEnvVarsByIDs(task.Envs) + // 使用任务配置的超时时间 timeout := task.Timeout if timeout <= 0 { timeout = constant.DefaultTaskTimeout } - result := es.ExecuteCommandWithTimeout(task.Command, time.Duration(timeout)*time.Minute) + result := es.ExecuteCommandWithEnv(task.Command, time.Duration(timeout)*time.Minute, envVars) result.TaskID = taskID // 标记任务结束 @@ -212,6 +217,11 @@ func (es *ExecutorService) ExecuteCommand(command string) *ExecutionResult { // ExecuteCommandWithTimeout executes a shell command with specified timeout func (es *ExecutorService) ExecuteCommandWithTimeout(command string, timeout time.Duration) *ExecutionResult { + return es.ExecuteCommandWithEnv(command, timeout, nil) +} + +// ExecuteCommandWithEnv executes a shell command with specified timeout and environment variables +func (es *ExecutorService) ExecuteCommandWithEnv(command string, timeout time.Duration, envVars []string) *ExecutionResult { result := &ExecutionResult{ Success: false, Start: time.Now(), @@ -226,6 +236,11 @@ func (es *ExecutorService) ExecuteCommandWithTimeout(command string, timeout tim cmd.Stdout = &stdout cmd.Stderr = &stderr + // 设置环境变量:继承系统环境变量 + 自定义环境变量 + if len(envVars) > 0 { + cmd.Env = append(os.Environ(), envVars...) + } + err := cmd.Run() result.End = time.Now() diff --git a/internal/services/task_service.go b/internal/services/task_service.go index 1e997f3..175aaa6 100644 --- a/internal/services/task_service.go +++ b/internal/services/task_service.go @@ -11,13 +11,14 @@ func NewTaskService() *TaskService { return &TaskService{} } -func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, cleanConfig string) *models.Task { +func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, cleanConfig, envs string) *models.Task { task := &models.Task{ Name: name, Command: command, Schedule: schedule, Timeout: timeout, CleanConfig: cleanConfig, + Envs: envs, Enabled: true, } database.DB.Create(task) @@ -54,7 +55,7 @@ func (ts *TaskService) GetTaskByID(id int) *models.Task { return &task } -func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeout int, cleanConfig string, enabled bool) *models.Task { +func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeout int, cleanConfig, envs string, enabled bool) *models.Task { var task models.Task if err := database.DB.First(&task, id).Error; err != nil { return nil @@ -64,6 +65,7 @@ func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeou task.Schedule = schedule task.Timeout = timeout task.CleanConfig = cleanConfig + task.Envs = envs task.Enabled = enabled database.DB.Save(&task) return &task diff --git a/web/src/api/index.ts b/web/src/api/index.ts index 0289864..c68d601 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -81,6 +81,7 @@ export const api = { if (params?.name) query.set('name', params.name) return request(`/env?${query}`) }, + all: () => request('/env/all'), create: (data: Partial) => request('/env', { method: 'POST', body: JSON.stringify(data) }), update: (id: number, data: Partial) => request(`/env/${id}`, { method: 'PUT', body: JSON.stringify(data) }), delete: (id: number) => request(`/env/${id}`, { method: 'DELETE' }) @@ -109,8 +110,6 @@ export const api = { settings: { changePassword: (data: { old_password: string; new_password: string }) => request('/settings/password', { method: 'POST', body: JSON.stringify(data) }), - cleanLogs: (days: number) => - request<{ deleted: number }>('/settings/cleanlogs', { method: 'POST', body: JSON.stringify({ days }) }), getSite: () => request('/settings/site'), getPublicSite: () => request<{ title: string; subtitle: string; icon: string }>('/settings/public'), updateSite: (data: SiteSettings) => @@ -188,6 +187,7 @@ export interface Task { schedule: string timeout: number clean_config: string + envs: string enabled: boolean last_run: string next_run: string diff --git a/web/src/components/ui/checkbox/Checkbox.vue b/web/src/components/ui/checkbox/Checkbox.vue new file mode 100644 index 0000000..6604cbd --- /dev/null +++ b/web/src/components/ui/checkbox/Checkbox.vue @@ -0,0 +1,35 @@ + + + diff --git a/web/src/components/ui/checkbox/index.ts b/web/src/components/ui/checkbox/index.ts new file mode 100644 index 0000000..3391a85 --- /dev/null +++ b/web/src/components/ui/checkbox/index.ts @@ -0,0 +1 @@ +export { default as Checkbox } from "./Checkbox.vue" diff --git a/web/src/components/ui/popover/Popover.vue b/web/src/components/ui/popover/Popover.vue new file mode 100644 index 0000000..4efdb98 --- /dev/null +++ b/web/src/components/ui/popover/Popover.vue @@ -0,0 +1,19 @@ + + + diff --git a/web/src/components/ui/popover/PopoverAnchor.vue b/web/src/components/ui/popover/PopoverAnchor.vue new file mode 100644 index 0000000..49e01db --- /dev/null +++ b/web/src/components/ui/popover/PopoverAnchor.vue @@ -0,0 +1,15 @@ + + + diff --git a/web/src/components/ui/popover/PopoverContent.vue b/web/src/components/ui/popover/PopoverContent.vue new file mode 100644 index 0000000..cf1e55c --- /dev/null +++ b/web/src/components/ui/popover/PopoverContent.vue @@ -0,0 +1,45 @@ + + + diff --git a/web/src/components/ui/popover/PopoverTrigger.vue b/web/src/components/ui/popover/PopoverTrigger.vue new file mode 100644 index 0000000..fd3b497 --- /dev/null +++ b/web/src/components/ui/popover/PopoverTrigger.vue @@ -0,0 +1,15 @@ + + + diff --git a/web/src/components/ui/popover/index.ts b/web/src/components/ui/popover/index.ts new file mode 100644 index 0000000..66edf89 --- /dev/null +++ b/web/src/components/ui/popover/index.ts @@ -0,0 +1,4 @@ +export { default as Popover } from "./Popover.vue" +export { default as PopoverAnchor } from "./PopoverAnchor.vue" +export { default as PopoverContent } from "./PopoverContent.vue" +export { default as PopoverTrigger } from "./PopoverTrigger.vue" diff --git a/web/src/views/history/History.vue b/web/src/views/history/History.vue index 54944b0..7af7147 100644 --- a/web/src/views/history/History.vue +++ b/web/src/views/history/History.vue @@ -1,5 +1,6 @@