From 3ab1ddc03ad52c34c47decbbd46793d3c9a73186 Mon Sep 17 00:00:00 2001 From: engigu Date: Fri, 13 Feb 2026 19:26:20 +0800 Subject: [PATCH] feat: add multi env setting support --- agent/agent.go | 50 ++-- internal/controllers/task_controller.go | 52 ++-- internal/executor/cron.go | 22 +- internal/executor/executor.go | 43 ++-- internal/executor/scheduler.go | 38 ++- internal/models/agent.go | 19 +- internal/models/task.go | 47 ++-- internal/models/vo/task_vo.go | 38 ++- internal/services/agent_service.go | 19 +- internal/services/tasks/executor_service.go | 29 +-- internal/services/tasks/task_service.go | 10 +- web/src/api/index.ts | 3 +- web/src/views/tasks/TaskDialog.vue | 254 +++++++++++--------- 13 files changed, 322 insertions(+), 302 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 579a743..15ccc8a 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -43,17 +43,16 @@ type WSMessage struct { } type AgentTask struct { - ID uint `json:"id"` - Name string `json:"name"` - Command string `json:"command"` - Schedule string `json:"schedule"` - Cron string `json:"cron"` - Timeout int `json:"timeout"` - WorkDir string `json:"work_dir"` - Envs string `json:"envs"` - Language string `json:"language"` - LangVersion string `json:"lang_version"` - Enabled bool `json:"enabled"` + ID uint `json:"id"` + Name string `json:"name"` + Command string `json:"command"` + Schedule string `json:"schedule"` + Cron string `json:"cron"` + Timeout int `json:"timeout"` + WorkDir string `json:"work_dir"` + Envs string `json:"envs"` + Languages []map[string]string `json:"languages"` + Enabled bool `json:"enabled"` } func (t *AgentTask) GetID() string { @@ -80,12 +79,8 @@ func (t *AgentTask) GetEnvs() string { return t.Envs } -func (t *AgentTask) GetLanguage() string { - return t.Language -} - -func (t *AgentTask) GetLangVersion() string { - return t.LangVersion +func (t *AgentTask) GetLanguages() []map[string]string { + return t.Languages } func (t *AgentTask) GetUseMise() bool { @@ -513,17 +508,16 @@ func (a *Agent) handleExecute(data json.RawMessage) { // 准备执行请求 execReq := &executor.ExecutionRequest{ - TaskID: fmt.Sprintf("%d", task.ID), - LogID: req.LogID, - Name: task.Name, - Command: task.Command, - WorkDir: task.WorkDir, - Envs: executor.ParseEnvVars(task.Envs), - Timeout: task.Timeout, - Language: task.Language, - LangVersion: task.LangVersion, - UseMise: false, - Type: executor.TaskTypeManual, + TaskID: fmt.Sprintf("%d", task.ID), + LogID: req.LogID, + Name: task.Name, + Command: task.Command, + WorkDir: task.WorkDir, + Envs: executor.ParseEnvVars(task.Envs), + Timeout: task.Timeout, + Languages: task.Languages, + UseMise: false, + Type: executor.TaskTypeManual, } // 立即执行任务(加入队列) diff --git a/internal/controllers/task_controller.go b/internal/controllers/task_controller.go index 79d3e87..3bba17c 100644 --- a/internal/controllers/task_controller.go +++ b/internal/controllers/task_controller.go @@ -52,18 +52,17 @@ func resolveWorkDir(workDir string) string { func (tc *TaskController) CreateTask(c *gin.Context) { var req struct { - Name string `json:"name" binding:"required"` - Command string `json:"command"` - Type string `json:"type"` - Config string `json:"config"` - Schedule string `json:"schedule" binding:"required"` - Timeout int `json:"timeout"` - WorkDir string `json:"work_dir"` - CleanConfig string `json:"clean_config"` - Envs string `json:"envs"` - Language string `json:"language"` - LangVersion string `json:"lang_version"` - AgentID *uint `json:"agent_id"` + Name string `json:"name" binding:"required"` + Command string `json:"command"` + Type string `json:"type"` + Config string `json:"config"` + Schedule string `json:"schedule" binding:"required"` + Timeout int `json:"timeout"` + WorkDir string `json:"work_dir"` + CleanConfig string `json:"clean_config"` + Envs string `json:"envs"` + Languages []map[string]string `json:"languages"` + AgentID *uint `json:"agent_id"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -88,7 +87,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) { workDir = resolveWorkDir(req.WorkDir) } - task := tc.taskService.CreateTask(req.Name, req.Command, req.Schedule, req.Timeout, workDir, req.CleanConfig, req.Envs, req.Type, req.Config, req.AgentID, req.Language, req.LangVersion) + task := tc.taskService.CreateTask(req.Name, req.Command, req.Schedule, req.Timeout, workDir, req.CleanConfig, req.Envs, req.Type, req.Config, req.AgentID, req.Languages) // 如果是 Agent 任务,通知 Agent;否则添加到本地 cron if task.AgentID != nil && *task.AgentID > 0 { @@ -148,19 +147,18 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { } var req struct { - Name string `json:"name"` - Command string `json:"command"` - Type string `json:"type"` - Config string `json:"config"` - 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"` - Language string `json:"language"` - LangVersion string `json:"lang_version"` - AgentID *uint `json:"agent_id"` + Name string `json:"name"` + Command string `json:"command"` + Type string `json:"type"` + Config string `json:"config"` + 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"` + Languages []map[string]string `json:"languages"` + AgentID *uint `json:"agent_id"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -181,7 +179,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { workDir = resolveWorkDir(req.WorkDir) } - task := tc.taskService.UpdateTask(id, req.Name, req.Command, req.Schedule, req.Timeout, workDir, req.CleanConfig, req.Envs, req.Enabled, req.Type, req.Config, req.AgentID, req.Language, req.LangVersion) + task := tc.taskService.UpdateTask(id, req.Name, req.Command, req.Schedule, req.Timeout, workDir, req.CleanConfig, req.Envs, req.Enabled, req.Type, req.Config, req.AgentID, req.Languages) if task == nil { utils.NotFound(c, "任务不存在") return diff --git a/internal/executor/cron.go b/internal/executor/cron.go index 9a3c2cb..690052a 100644 --- a/internal/executor/cron.go +++ b/internal/executor/cron.go @@ -78,8 +78,7 @@ func (m *CronManager) AddTask(task CronTask) error { timeout := task.GetTimeout() workDir := task.GetWorkDir() envs := task.GetEnvs() - language := task.GetLanguage() - langVersion := task.GetLangVersion() + languages := task.GetLanguages() useMise := task.UseMise() entryID, err := m.cron.AddFunc(task.GetSchedule(), func() { @@ -91,16 +90,15 @@ func (m *CronManager) AddTask(task CronTask) error { m.logger.Infof("[CronManager] 触发计划任务 #%s (%s)", taskID, name) req := &ExecutionRequest{ - TaskID: taskID, - Name: name, - Command: cmd, - Type: TaskTypeCron, - Timeout: timeout, - WorkDir: workDir, - Envs: ParseEnvVars(envs), - Language: language, - LangVersion: langVersion, - UseMise: useMise, + TaskID: taskID, + Name: name, + Command: cmd, + Type: TaskTypeCron, + Timeout: timeout, + WorkDir: workDir, + Envs: ParseEnvVars(envs), + Languages: languages, + UseMise: useMise, } // 如果有关联的 Scheduler,加入队列执行 diff --git a/internal/executor/executor.go b/internal/executor/executor.go index 7632d8e..d843f2c 100644 --- a/internal/executor/executor.go +++ b/internal/executor/executor.go @@ -23,8 +23,7 @@ type Task interface { GetTimeout() int GetWorkDir() string GetEnvs() string - GetLanguage() string - GetLangVersion() string + GetLanguages() []map[string]string GetUseMise() bool } @@ -37,13 +36,12 @@ type CronTask interface { // Request 任务执行请求 type Request struct { - Command string - WorkDir string - Envs []string - Timeout int // 任务超时时间(分钟) - Language string - LangVersion string - UseMise bool + Command string + WorkDir string + Envs []string + Timeout int // 任务超时时间(分钟) + Languages []map[string]string + UseMise bool } // Result 任务执行结果 @@ -104,7 +102,7 @@ func ExecuteWithHooks(ctx context.Context, req Request, stdout, stderr io.Writer finalCommand := req.Command if req.UseMise { - finalCommand = BuildLanguageCommand(req.Command, req.Language, req.LangVersion) + finalCommand = BuildLanguageCommand(req.Command, req.Languages) } shell, args := utils.GetShellCommand(finalCommand) cmd := exec.CommandContext(execCtx, shell, args...) @@ -283,15 +281,28 @@ func ExecuteWithHooks(ctx context.Context, req Request, stdout, stderr io.Writer } // BuildLanguageCommand 构建语言环境执行命令 (使用 mise) -func BuildLanguageCommand(command, language, version string) string { - if language == "" { +func BuildLanguageCommand(command string, languages []map[string]string) string { + if len(languages) == 0 { return command } - v := version - if v == "" { - v = "latest" + + var builder strings.Builder + builder.WriteString("mise exec") + + for _, lang := range languages { + name := lang["name"] + version := lang["version"] + if name == "" { + continue + } + if version == "" { + version = "latest" + } + builder.WriteString(" " + name + "@" + version) } - return "mise exec " + language + "@" + v + " -- " + command + + builder.WriteString(" -- " + command) + return builder.String() } // ParseEnvVars 解析环境变量字符串 "KEY1=VALUE1,KEY2=VALUE2" diff --git a/internal/executor/scheduler.go b/internal/executor/scheduler.go index 90d7e66..9d483c3 100644 --- a/internal/executor/scheduler.go +++ b/internal/executor/scheduler.go @@ -61,18 +61,17 @@ const ( // ExecutionRequest 执行请求(标准接口) type ExecutionRequest struct { - TaskID string // 任务 ID - LogID uint // 日志 ID - Name string // 任务名称 - Type TaskType // 任务类型 - Command string // 命令 - WorkDir string // 工作目录 - Envs []string // 环境变量 - Timeout int // 超时时间(分钟) - Language string // 语言环境 - LangVersion string // 语言版本 - UseMise bool // 是否使用 mise - Metadata map[string]interface{} // 额外元数据 + TaskID string // 任务 ID + LogID uint // 日志 ID + Name string // 任务名称 + Type TaskType // 任务类型 + Command string // 命令 + WorkDir string // 工作目录 + Envs []string // 环境变量 + Timeout int // 超时时间(分钟) + Languages []map[string]string // 语言环境配置 + UseMise bool // 是否使用 mise + Metadata map[string]interface{} // 额外元数据 } // ExecutionResult 执行结果(标准接口) @@ -197,13 +196,12 @@ func NewScheduler(config SchedulerConfig, handler SchedulerEventHandler) *Schedu executor: func(ctx context.Context, req *ExecutionRequest, stdout, stderr io.Writer) (*Result, error) { hooks := &schedulerHooksAdapter{handler: handler, req: req} return ExecuteWithHooks(ctx, Request{ - Command: req.Command, - WorkDir: req.WorkDir, - Envs: req.Envs, - Timeout: req.Timeout, - Language: req.Language, - LangVersion: req.LangVersion, - UseMise: req.UseMise, + Command: req.Command, + WorkDir: req.WorkDir, + Envs: req.Envs, + Timeout: req.Timeout, + Languages: req.Languages, + UseMise: req.UseMise, }, stdout, stderr, hooks) }, taskQueue: make(chan *ExecutionRequest, config.QueueSize), @@ -317,7 +315,7 @@ func (s *Scheduler) executeTask(req *ExecutionRequest) (*ExecutionResult, error) finalCommand := req.Command if req.UseMise { - finalCommand = BuildLanguageCommand(req.Command, req.Language, req.LangVersion) + finalCommand = BuildLanguageCommand(req.Command, req.Languages) } s.logger.Infof("[Scheduler] 实际执行命令: %s", finalCommand) diff --git a/internal/models/agent.go b/internal/models/agent.go index 23c6ef8..d34be0d 100644 --- a/internal/models/agent.go +++ b/internal/models/agent.go @@ -52,16 +52,15 @@ func (AgentToken) TableName() string { // AgentTask Agent 任务配置(用于下发给 Agent) type AgentTask struct { - ID uint `json:"id"` - Name string `json:"name"` - Command string `json:"command"` - Schedule string `json:"schedule"` - Timeout int `json:"timeout"` - WorkDir string `json:"work_dir"` - Envs string `json:"envs"` - Language string `json:"language"` - LangVersion string `json:"lang_version"` - Enabled bool `json:"enabled"` + ID uint `json:"id"` + Name string `json:"name"` + Command string `json:"command"` + Schedule string `json:"schedule"` + Timeout int `json:"timeout"` + WorkDir string `json:"work_dir"` + Envs string `json:"envs"` + Languages []map[string]string `json:"languages"` + Enabled bool `json:"enabled"` } // AgentTaskResult Agent 上报的任务执行结果 diff --git a/internal/models/task.go b/internal/models/task.go index ceff80e..535b7b9 100644 --- a/internal/models/task.go +++ b/internal/models/task.go @@ -34,26 +34,25 @@ type TaskConfig struct { // Task 代表一个计划任务 type Task struct { - ID uint `json:"id" gorm:"primaryKey"` - Name string `json:"name" gorm:"size:255;not null"` - Command string `json:"command" gorm:"type:text"` // 普通任务的命令 - Type string `json:"type" gorm:"size:20;default:'task'"` // 任务类型: constant.TaskTypeNormal, constant.TaskTypeRepo - Config string `json:"config" gorm:"type:text"` // 配置 JSON(仓库同步配置等) - Schedule string `json:"schedule" gorm:"size:100"` // cron 表达式 - 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列表,逗号分隔 - Language string `json:"language" gorm:"size:50;default:''"` // 针对本地任务的语言名称 (mise) - LangVersion string `json:"lang_version" gorm:"size:50;default:''"` // 针对本地任务的语言版本 (mise) - AgentID *uint `json:"agent_id" gorm:"index"` // Agent ID,为空表示本地执行 - Enabled bool `json:"enabled" gorm:"default:true"` - RunningGo string `json:"running_go" gorm:"type:text"` // 正在运行的 go routine id 数组 (JSON) - LastRun *LocalTime `json:"last_run"` - NextRun *LocalTime `json:"next_run"` - CreatedAt LocalTime `json:"created_at"` - UpdatedAt LocalTime `json:"updated_at"` - DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + ID uint `json:"id" gorm:"primaryKey"` + Name string `json:"name" gorm:"size:255;not null"` + Command string `json:"command" gorm:"type:text"` // 普通任务的命令 + Type string `json:"type" gorm:"size:20;default:'task'"` // 任务类型: constant.TaskTypeNormal, constant.TaskTypeRepo + Config string `json:"config" gorm:"type:text"` // 配置 JSON(仓库同步配置等) + Schedule string `json:"schedule" gorm:"size:100"` // cron 表达式 + 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列表,逗号分隔 + Languages []map[string]string `json:"languages" gorm:"serializer:json;type:text"` // 针对本地任务的语言配置列表 + AgentID *uint `json:"agent_id" gorm:"index"` // Agent ID,为空表示本地执行 + Enabled bool `json:"enabled" gorm:"default:true"` + RunningGo string `json:"running_go" gorm:"type:text"` // 正在运行的 go routine id 数组 (JSON) + LastRun *LocalTime `json:"last_run"` + NextRun *LocalTime `json:"next_run"` + CreatedAt LocalTime `json:"created_at"` + UpdatedAt LocalTime `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` } func (Task) TableName() string { @@ -84,12 +83,8 @@ func (t *Task) GetEnvs() string { return t.Envs } -func (t *Task) GetLanguage() string { - return t.Language -} - -func (t *Task) GetLangVersion() string { - return t.LangVersion +func (t *Task) GetLanguages() []map[string]string { + return t.Languages } func (t *Task) GetUseMise() bool { diff --git a/internal/models/vo/task_vo.go b/internal/models/vo/task_vo.go index 8ce0b35..58253c7 100644 --- a/internal/models/vo/task_vo.go +++ b/internal/models/vo/task_vo.go @@ -7,24 +7,23 @@ import ( // TaskVO 任务视图对象 type TaskVO struct { - ID uint `json:"id"` - Name string `json:"name"` - Command string `json:"command"` - Type string `json:"type"` - Config string `json:"config"` - Schedule string `json:"schedule"` - Timeout int `json:"timeout"` - WorkDir string `json:"work_dir"` - CleanConfig string `json:"clean_config"` - Envs string `json:"envs"` - Language string `json:"language"` - LangVersion string `json:"lang_version"` - AgentID *uint `json:"agent_id"` - Enabled bool `json:"enabled"` - LastRun *models.LocalTime `json:"last_run"` - NextRun *models.LocalTime `json:"next_run"` - CreatedAt models.LocalTime `json:"created_at"` - UpdatedAt models.LocalTime `json:"updated_at"` + ID uint `json:"id"` + Name string `json:"name"` + Command string `json:"command"` + Type string `json:"type"` + Config string `json:"config"` + Schedule string `json:"schedule"` + Timeout int `json:"timeout"` + WorkDir string `json:"work_dir"` + CleanConfig string `json:"clean_config"` + Envs string `json:"envs"` + Languages []map[string]string `json:"languages"` + AgentID *uint `json:"agent_id"` + Enabled bool `json:"enabled"` + LastRun *models.LocalTime `json:"last_run"` + NextRun *models.LocalTime `json:"next_run"` + CreatedAt models.LocalTime `json:"created_at"` + UpdatedAt models.LocalTime `json:"updated_at"` } // ToTaskVO 将 Task 模型转换为 TaskVO @@ -43,8 +42,7 @@ func ToTaskVO(task *models.Task) *TaskVO { WorkDir: task.WorkDir, CleanConfig: task.CleanConfig, Envs: task.Envs, - Language: task.Language, - LangVersion: task.LangVersion, + Languages: task.Languages, AgentID: task.AgentID, Enabled: task.Enabled, LastRun: task.LastRun, diff --git a/internal/services/agent_service.go b/internal/services/agent_service.go index 06a2e63..ada9ca6 100644 --- a/internal/services/agent_service.go +++ b/internal/services/agent_service.go @@ -311,16 +311,15 @@ func (s *AgentService) GetTasks(agentID uint) []models.AgentTask { envVarsStr := s.buildEnvVarsString(task.Envs) result[i] = models.AgentTask{ - ID: task.ID, - Name: task.Name, - Command: task.Command, - Schedule: task.Schedule, - Timeout: task.Timeout, - WorkDir: task.WorkDir, - Envs: envVarsStr, // 传递 "KEY1=VALUE1,KEY2=VALUE2" 格式 - Language: task.Language, - LangVersion: task.LangVersion, - Enabled: task.Enabled, + ID: task.ID, + Name: task.Name, + Command: task.Command, + Schedule: task.Schedule, + Timeout: task.Timeout, + WorkDir: task.WorkDir, + Envs: envVarsStr, // 传递 "KEY1=VALUE1,KEY2=VALUE2" 格式 + Languages: task.Languages, + Enabled: task.Enabled, } } diff --git a/internal/services/tasks/executor_service.go b/internal/services/tasks/executor_service.go index 847790f..7f20dd7 100644 --- a/internal/services/tasks/executor_service.go +++ b/internal/services/tasks/executor_service.go @@ -352,13 +352,12 @@ func (es *ExecutorService) ExecuteDispatcher(ctx context.Context, req *executor. // 本地任务 hooks := &LocalTaskHooks{es: es, logID: req.LogID} return executor.ExecuteWithHooks(ctx, executor.Request{ - Command: req.Command, - WorkDir: req.WorkDir, - Envs: req.Envs, - Timeout: req.Timeout, - Language: task.Language, - LangVersion: task.LangVersion, - UseMise: true, // 本地业务任务启用 mise + Command: req.Command, + WorkDir: req.WorkDir, + Envs: req.Envs, + Timeout: req.Timeout, + Languages: task.Languages, + UseMise: true, // 本地业务任务启用 mise }, stdout, stderr, hooks) } @@ -470,13 +469,15 @@ func (es *ExecutorService) ExecuteTask(taskID int, extraEnvs []string) *executor } req := &executor.ExecutionRequest{ - TaskID: fmt.Sprintf("%d", task.ID), - Name: task.Name, - Command: task.Command, - WorkDir: task.WorkDir, - Envs: envs, - Timeout: task.Timeout, - Type: executor.TaskTypeManual, + TaskID: fmt.Sprintf("%d", task.ID), + Name: task.Name, + Command: task.Command, + WorkDir: task.WorkDir, + Envs: envs, + Timeout: task.Timeout, + Languages: task.Languages, + UseMise: task.UseMise(), + Type: executor.TaskTypeManual, } es.scheduler.EnqueueOrExecute(req) diff --git a/internal/services/tasks/task_service.go b/internal/services/tasks/task_service.go index 1a0a29f..8744d4e 100644 --- a/internal/services/tasks/task_service.go +++ b/internal/services/tasks/task_service.go @@ -11,7 +11,7 @@ func NewTaskService() *TaskService { return &TaskService{} } -func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, workDir, cleanConfig, envs, taskType, config string, agentID *uint, language, langVersion string) *models.Task { +func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, workDir, cleanConfig, envs, taskType, config string, agentID *uint, languages []map[string]string) *models.Task { if taskType == "" { taskType = "task" } @@ -25,8 +25,7 @@ func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, w WorkDir: workDir, CleanConfig: cleanConfig, Envs: envs, - Language: language, - LangVersion: langVersion, + Languages: languages, AgentID: agentID, Enabled: true, } @@ -67,7 +66,7 @@ func (ts *TaskService) GetTaskByID(id int) *models.Task { return &task } -func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeout int, workDir, cleanConfig, envs string, enabled bool, taskType, config string, agentID *uint, language, langVersion string) *models.Task { +func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeout int, workDir, cleanConfig, envs string, enabled bool, taskType, config string, agentID *uint, languages []map[string]string) *models.Task { var task models.Task if err := database.DB.First(&task, id).Error; err != nil { return nil @@ -81,8 +80,7 @@ func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeou task.Envs = envs task.Enabled = enabled task.AgentID = agentID - task.Language = language - task.LangVersion = langVersion + task.Languages = languages if taskType != "" { task.Type = taskType } diff --git a/web/src/api/index.ts b/web/src/api/index.ts index c3f11c2..46cbb47 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -271,8 +271,7 @@ export interface Task { work_dir: string clean_config: string envs: string - language: string - lang_version: string + languages: { name: string; version: string }[] agent_id: number | null enabled: boolean last_run: string diff --git a/web/src/views/tasks/TaskDialog.vue b/web/src/views/tasks/TaskDialog.vue index 0ee821f..c2b35c4 100644 --- a/web/src/views/tasks/TaskDialog.vue +++ b/web/src/views/tasks/TaskDialog.vue @@ -89,13 +89,11 @@ const onlineAgents = computed(() => { // 语言环境相关 const installedLangs = ref([]) const loadingLangs = ref(false) +const selectedLangs = ref<{ name: string; version: string; availableVersions: string[] }[]>([]) + const availablePlugins = ref([]) const pluginSearch = ref('') -const openPluginPopover = ref(false) - -const availableVersions = ref([]) const versionSearch = ref('') -const openVersionPopover = ref(false) const filteredPlugins = computed(() => { if (!pluginSearch.value) return availablePlugins.value @@ -103,11 +101,11 @@ const filteredPlugins = computed(() => { return availablePlugins.value.filter(p => p.toLowerCase().includes(s)) }) -const filteredVersions = computed(() => { - if (!versionSearch.value) return availableVersions.value +function getFilteredVersions(versions: string[]) { + if (!versionSearch.value) return versions const s = versionSearch.value.toLowerCase() - return availableVersions.value.filter(v => v.toLowerCase().includes(s)) -}) + return versions.filter(v => v.toLowerCase().includes(s)) +} async function fetchInstalledLangs() { loadingLangs.value = true @@ -163,22 +161,32 @@ function getLangIcon(plugin: string) { return '' } -watch(() => form.value.language, (newVal) => { - if (newVal) { - availableVersions.value = installedLangs.value - .filter(l => l.plugin === newVal) +function updateAvailableVersions(lang: { name: string; version: string; availableVersions: string[] }) { + if (lang.name) { + lang.availableVersions = installedLangs.value + .filter(l => l.plugin === lang.name) .map(l => l.version) .sort((a, b) => b.localeCompare(a, undefined, { numeric: true })) - - // 如果当前选中的版本不在可用列表中,且列表不为空,则自动选中第一个(通常是最新版) - if (form.value.lang_version && !availableVersions.value.includes(form.value.lang_version)) { - // 保持原样,可能还没加载完 - } } else { - availableVersions.value = [] - form.value.lang_version = '' + lang.availableVersions = [] } -}) +} + +function addLang() { + selectedLangs.value.push({ name: '', version: '', availableVersions: [] }) +} + +function removeLang(index: number) { + selectedLangs.value.splice(index, 1) +} + +function updateLangName(index: number, name: string) { + const lang = selectedLangs.value[index] + if (!lang) return + lang.name = name + lang.version = '' // reset version + updateAvailableVersions(lang) +} watch(() => props.open, async (val) => { if (val) { @@ -233,6 +241,16 @@ watch(() => props.open, async (val) => { } else { selectedEnvIds.value = [] } + // 解析语言环境 + selectedLangs.value = [] + if (props.task?.languages && Array.isArray(props.task.languages)) { + selectedLangs.value = props.task.languages.map((l: any) => ({ + name: l.name || '', + version: l.version || '', + availableVersions: [] + })) + } + // 解析 Agent 和工作目录 const agentId = props.task?.agent_id ? String(props.task.agent_id) : 'local' selectedAgentId.value = agentId @@ -245,13 +263,10 @@ watch(() => props.open, async (val) => { await loadData() if (selectedAgentId.value === 'local') { await fetchInstalledLangs() - // 如果已选择语言(编辑模式),手动触发一次版本更新 - if (form.value.language) { - availableVersions.value = installedLangs.value - .filter(l => l.plugin === form.value.language) - .map(l => l.version) - .sort((a, b) => b.localeCompare(a, undefined, { numeric: true })) - } + // 更新所有语言的可用版本 + selectedLangs.value.forEach(lang => { + updateAvailableVersions(lang) + }) } } }) @@ -285,6 +300,12 @@ async function save() { form.value.type = 'task' form.value.agent_id = selectedAgentId.value === 'local' ? null : Number(selectedAgentId.value) + // 保存语言环境配置 + form.value.languages = selectedLangs.value.map(l => ({ + name: l.name, + version: l.version + })) + // 保存配置 - 确保 concurrency 字段被正确保存 let config: Record = {} @@ -365,93 +386,104 @@ async function save() { -
- -
- - - - - -
-
- - -
-
- -
-
- -
-
- 未找到已安装语言 -
- -
-
-
-
- - - - - - -
-
- - -
-
- -
-
- 无可用版本 + {{ clang.name || "选择环境..." }} +
+ + + + +
+
+ +
-
- -
- + +
+
+ +
+
+ 未找到已安装语言 +
+ +
+
+ + + + + + + + +
+
+ + +
+
+ +
+
+ 无可用版本 +
+ +
+
+
+
+ + +
+ +