diff --git a/internal/controllers/task_controller.go b/internal/controllers/task_controller.go index 5d35ccb..705001d 100644 --- a/internal/controllers/task_controller.go +++ b/internal/controllers/task_controller.go @@ -27,6 +27,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) { Command string `json:"command" binding:"required"` Schedule string `json:"schedule" binding:"required"` Timeout int `json:"timeout"` + WorkDir string `json:"work_dir"` CleanConfig string `json:"clean_config"` Envs string `json:"envs"` } @@ -41,7 +42,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) { return } - task := tc.taskService.CreateTask(req.Name, req.Command, req.Schedule, req.Timeout, req.CleanConfig, req.Envs) + task := tc.taskService.CreateTask(req.Name, req.Command, req.Schedule, req.Timeout, req.WorkDir, req.CleanConfig, req.Envs) tc.cronService.AddTask(task) utils.Success(c, task) @@ -83,6 +84,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { Command string `json:"command"` Schedule string `json:"schedule"` Timeout int `json:"timeout"` + WorkDir string `json:"work_dir"` CleanConfig string `json:"clean_config"` Envs string `json:"envs"` Enabled bool `json:"enabled"` @@ -100,7 +102,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { } } - task := tc.taskService.UpdateTask(id, req.Name, req.Command, req.Schedule, req.Timeout, req.CleanConfig, req.Envs, req.Enabled) + task := tc.taskService.UpdateTask(id, req.Name, req.Command, req.Schedule, req.Timeout, req.WorkDir, 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 08f888c..a751c53 100644 --- a/internal/models/task.go +++ b/internal/models/task.go @@ -19,6 +19,7 @@ type Task struct { Command string `json:"command" gorm:"type:text;not null"` Schedule string `json:"schedule" gorm:"size:100"` // cron expression Timeout int `json:"timeout" gorm:"default:30"` // 超时时间(分钟),默认30分钟 + WorkDir string `json:"work_dir" gorm:"size:255;default:''"` // 工作目录,为空则使用 scripts 目录 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"` diff --git a/internal/services/executor_service.go b/internal/services/executor_service.go index 8d6f7d7..4f097f8 100644 --- a/internal/services/executor_service.go +++ b/internal/services/executor_service.go @@ -300,12 +300,18 @@ func (es *ExecutorService) executeTaskInternal(taskID int) *ExecutionResult { envService := NewEnvService() envVars := envService.GetEnvVarsByIDs(task.Envs) + // 确定工作目录 + workDir := task.WorkDir + if workDir == "" { + workDir = constant.ScriptsWorkDir + } + // 使用任务配置的超时时间 timeout := task.Timeout if timeout <= 0 { timeout = constant.DefaultTaskTimeout } - result := es.ExecuteCommandWithEnv(task.Command, time.Duration(timeout)*time.Minute, envVars) + result := es.ExecuteCommandWithOptions(task.Command, time.Duration(timeout)*time.Minute, envVars, workDir) result.TaskID = taskID // 标记任务结束 @@ -338,6 +344,11 @@ func (es *ExecutorService) ExecuteCommandWithTimeout(command string, timeout tim // ExecuteCommandWithEnv executes a shell command with specified timeout and environment variables func (es *ExecutorService) ExecuteCommandWithEnv(command string, timeout time.Duration, envVars []string) *ExecutionResult { + return es.ExecuteCommandWithOptions(command, timeout, envVars, "") +} + +// ExecuteCommandWithOptions executes a shell command with specified timeout, environment variables and working directory +func (es *ExecutorService) ExecuteCommandWithOptions(command string, timeout time.Duration, envVars []string, workDir string) *ExecutionResult { result := &ExecutionResult{ Success: false, Start: time.Now(), @@ -352,6 +363,11 @@ func (es *ExecutorService) ExecuteCommandWithEnv(command string, timeout time.Du cmd.Stdout = &stdout cmd.Stderr = &stderr + // 设置工作目录 + if workDir != "" { + cmd.Dir = workDir + } + // 设置环境变量:继承系统环境变量 + 自定义环境变量 if len(envVars) > 0 { cmd.Env = append(os.Environ(), envVars...) diff --git a/internal/services/task_service.go b/internal/services/task_service.go index 175aaa6..fc1b798 100644 --- a/internal/services/task_service.go +++ b/internal/services/task_service.go @@ -11,12 +11,13 @@ func NewTaskService() *TaskService { return &TaskService{} } -func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, cleanConfig, envs string) *models.Task { +func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, workDir, cleanConfig, envs string) *models.Task { task := &models.Task{ Name: name, Command: command, Schedule: schedule, Timeout: timeout, + WorkDir: workDir, CleanConfig: cleanConfig, Envs: envs, Enabled: true, @@ -55,7 +56,7 @@ func (ts *TaskService) GetTaskByID(id int) *models.Task { return &task } -func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeout int, cleanConfig, envs string, enabled bool) *models.Task { +func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeout int, workDir, 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.Command = command task.Schedule = schedule task.Timeout = timeout + task.WorkDir = workDir task.CleanConfig = cleanConfig task.Envs = envs task.Enabled = enabled diff --git a/web/src/api/index.ts b/web/src/api/index.ts index efa975b..0de378d 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -220,6 +220,7 @@ export interface Task { command: string schedule: string timeout: number + work_dir: string clean_config: string envs: string enabled: boolean diff --git a/web/src/components/DirTreeSelect.vue b/web/src/components/DirTreeSelect.vue new file mode 100644 index 0000000..de8d777 --- /dev/null +++ b/web/src/components/DirTreeSelect.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/web/src/views/tasks/Tasks.vue b/web/src/views/tasks/Tasks.vue index 119130b..4eefc7f 100644 --- a/web/src/views/tasks/Tasks.vue +++ b/web/src/views/tasks/Tasks.vue @@ -9,6 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Badge } from '@/components/ui/badge' import Pagination from '@/components/Pagination.vue' +import DirTreeSelect from '@/components/DirTreeSelect.vue' import { Plus, Play, Pencil, Trash2, Search, ScrollText, ChevronDown, X } from 'lucide-vue-next' import { api, type Task, type EnvVar } from '@/api' import { toast } from 'vue-sonner' @@ -118,7 +119,7 @@ function handlePageChange(page: number) { } function openCreate() { - editingTask.value = { name: '', command: '', schedule: '0 * * * * *', timeout: 30, enabled: true, clean_config: '', envs: '' } + editingTask.value = { name: '', command: '', schedule: '0 * * * * *', timeout: 30, work_dir: '', enabled: true, clean_config: '', envs: '' } cleanType.value = 'none' cleanKeep.value = 30 selectedEnvIds.value = [] @@ -192,7 +193,7 @@ async function runTask(id: number) { async function toggleTask(task: Task, enabled: boolean) { try { - await api.tasks.update(task.id, { name: task.name, command: task.command, schedule: task.schedule, timeout: task.timeout, clean_config: task.clean_config, envs: task.envs, enabled }) + await api.tasks.update(task.id, { name: task.name, command: task.command, schedule: task.schedule, timeout: task.timeout, work_dir: task.work_dir, clean_config: task.clean_config, envs: task.envs, enabled }) toast.success(enabled ? '任务已启用' : '任务已禁用') loadTasks() } catch { toast.error('操作失败') } @@ -291,6 +292,12 @@ onMounted(() => { +
+ +
+ +
+