feat: add multi env setting support
This commit is contained in:
+4
-10
@@ -51,8 +51,7 @@ type AgentTask struct {
|
|||||||
Timeout int `json:"timeout"`
|
Timeout int `json:"timeout"`
|
||||||
WorkDir string `json:"work_dir"`
|
WorkDir string `json:"work_dir"`
|
||||||
Envs string `json:"envs"`
|
Envs string `json:"envs"`
|
||||||
Language string `json:"language"`
|
Languages []map[string]string `json:"languages"`
|
||||||
LangVersion string `json:"lang_version"`
|
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,12 +79,8 @@ func (t *AgentTask) GetEnvs() string {
|
|||||||
return t.Envs
|
return t.Envs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *AgentTask) GetLanguage() string {
|
func (t *AgentTask) GetLanguages() []map[string]string {
|
||||||
return t.Language
|
return t.Languages
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AgentTask) GetLangVersion() string {
|
|
||||||
return t.LangVersion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *AgentTask) GetUseMise() bool {
|
func (t *AgentTask) GetUseMise() bool {
|
||||||
@@ -520,8 +515,7 @@ func (a *Agent) handleExecute(data json.RawMessage) {
|
|||||||
WorkDir: task.WorkDir,
|
WorkDir: task.WorkDir,
|
||||||
Envs: executor.ParseEnvVars(task.Envs),
|
Envs: executor.ParseEnvVars(task.Envs),
|
||||||
Timeout: task.Timeout,
|
Timeout: task.Timeout,
|
||||||
Language: task.Language,
|
Languages: task.Languages,
|
||||||
LangVersion: task.LangVersion,
|
|
||||||
UseMise: false,
|
UseMise: false,
|
||||||
Type: executor.TaskTypeManual,
|
Type: executor.TaskTypeManual,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,8 +61,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) {
|
|||||||
WorkDir string `json:"work_dir"`
|
WorkDir string `json:"work_dir"`
|
||||||
CleanConfig string `json:"clean_config"`
|
CleanConfig string `json:"clean_config"`
|
||||||
Envs string `json:"envs"`
|
Envs string `json:"envs"`
|
||||||
Language string `json:"language"`
|
Languages []map[string]string `json:"languages"`
|
||||||
LangVersion string `json:"lang_version"`
|
|
||||||
AgentID *uint `json:"agent_id"`
|
AgentID *uint `json:"agent_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +87,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) {
|
|||||||
workDir = resolveWorkDir(req.WorkDir)
|
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
|
// 如果是 Agent 任务,通知 Agent;否则添加到本地 cron
|
||||||
if task.AgentID != nil && *task.AgentID > 0 {
|
if task.AgentID != nil && *task.AgentID > 0 {
|
||||||
@@ -158,8 +157,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) {
|
|||||||
CleanConfig string `json:"clean_config"`
|
CleanConfig string `json:"clean_config"`
|
||||||
Envs string `json:"envs"`
|
Envs string `json:"envs"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Language string `json:"language"`
|
Languages []map[string]string `json:"languages"`
|
||||||
LangVersion string `json:"lang_version"`
|
|
||||||
AgentID *uint `json:"agent_id"`
|
AgentID *uint `json:"agent_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +179,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) {
|
|||||||
workDir = resolveWorkDir(req.WorkDir)
|
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 {
|
if task == nil {
|
||||||
utils.NotFound(c, "任务不存在")
|
utils.NotFound(c, "任务不存在")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -78,8 +78,7 @@ func (m *CronManager) AddTask(task CronTask) error {
|
|||||||
timeout := task.GetTimeout()
|
timeout := task.GetTimeout()
|
||||||
workDir := task.GetWorkDir()
|
workDir := task.GetWorkDir()
|
||||||
envs := task.GetEnvs()
|
envs := task.GetEnvs()
|
||||||
language := task.GetLanguage()
|
languages := task.GetLanguages()
|
||||||
langVersion := task.GetLangVersion()
|
|
||||||
useMise := task.UseMise()
|
useMise := task.UseMise()
|
||||||
|
|
||||||
entryID, err := m.cron.AddFunc(task.GetSchedule(), func() {
|
entryID, err := m.cron.AddFunc(task.GetSchedule(), func() {
|
||||||
@@ -98,8 +97,7 @@ func (m *CronManager) AddTask(task CronTask) error {
|
|||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
WorkDir: workDir,
|
WorkDir: workDir,
|
||||||
Envs: ParseEnvVars(envs),
|
Envs: ParseEnvVars(envs),
|
||||||
Language: language,
|
Languages: languages,
|
||||||
LangVersion: langVersion,
|
|
||||||
UseMise: useMise,
|
UseMise: useMise,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ type Task interface {
|
|||||||
GetTimeout() int
|
GetTimeout() int
|
||||||
GetWorkDir() string
|
GetWorkDir() string
|
||||||
GetEnvs() string
|
GetEnvs() string
|
||||||
GetLanguage() string
|
GetLanguages() []map[string]string
|
||||||
GetLangVersion() string
|
|
||||||
GetUseMise() bool
|
GetUseMise() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +40,7 @@ type Request struct {
|
|||||||
WorkDir string
|
WorkDir string
|
||||||
Envs []string
|
Envs []string
|
||||||
Timeout int // 任务超时时间(分钟)
|
Timeout int // 任务超时时间(分钟)
|
||||||
Language string
|
Languages []map[string]string
|
||||||
LangVersion string
|
|
||||||
UseMise bool
|
UseMise bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +102,7 @@ func ExecuteWithHooks(ctx context.Context, req Request, stdout, stderr io.Writer
|
|||||||
|
|
||||||
finalCommand := req.Command
|
finalCommand := req.Command
|
||||||
if req.UseMise {
|
if req.UseMise {
|
||||||
finalCommand = BuildLanguageCommand(req.Command, req.Language, req.LangVersion)
|
finalCommand = BuildLanguageCommand(req.Command, req.Languages)
|
||||||
}
|
}
|
||||||
shell, args := utils.GetShellCommand(finalCommand)
|
shell, args := utils.GetShellCommand(finalCommand)
|
||||||
cmd := exec.CommandContext(execCtx, shell, args...)
|
cmd := exec.CommandContext(execCtx, shell, args...)
|
||||||
@@ -283,15 +281,28 @@ func ExecuteWithHooks(ctx context.Context, req Request, stdout, stderr io.Writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BuildLanguageCommand 构建语言环境执行命令 (使用 mise)
|
// BuildLanguageCommand 构建语言环境执行命令 (使用 mise)
|
||||||
func BuildLanguageCommand(command, language, version string) string {
|
func BuildLanguageCommand(command string, languages []map[string]string) string {
|
||||||
if language == "" {
|
if len(languages) == 0 {
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
v := version
|
|
||||||
if v == "" {
|
var builder strings.Builder
|
||||||
v = "latest"
|
builder.WriteString("mise exec")
|
||||||
|
|
||||||
|
for _, lang := range languages {
|
||||||
|
name := lang["name"]
|
||||||
|
version := lang["version"]
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
return "mise exec " + language + "@" + v + " -- " + command
|
if version == "" {
|
||||||
|
version = "latest"
|
||||||
|
}
|
||||||
|
builder.WriteString(" " + name + "@" + version)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString(" -- " + command)
|
||||||
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseEnvVars 解析环境变量字符串 "KEY1=VALUE1,KEY2=VALUE2"
|
// ParseEnvVars 解析环境变量字符串 "KEY1=VALUE1,KEY2=VALUE2"
|
||||||
|
|||||||
@@ -69,8 +69,7 @@ type ExecutionRequest struct {
|
|||||||
WorkDir string // 工作目录
|
WorkDir string // 工作目录
|
||||||
Envs []string // 环境变量
|
Envs []string // 环境变量
|
||||||
Timeout int // 超时时间(分钟)
|
Timeout int // 超时时间(分钟)
|
||||||
Language string // 语言环境
|
Languages []map[string]string // 语言环境配置
|
||||||
LangVersion string // 语言版本
|
|
||||||
UseMise bool // 是否使用 mise
|
UseMise bool // 是否使用 mise
|
||||||
Metadata map[string]interface{} // 额外元数据
|
Metadata map[string]interface{} // 额外元数据
|
||||||
}
|
}
|
||||||
@@ -201,8 +200,7 @@ func NewScheduler(config SchedulerConfig, handler SchedulerEventHandler) *Schedu
|
|||||||
WorkDir: req.WorkDir,
|
WorkDir: req.WorkDir,
|
||||||
Envs: req.Envs,
|
Envs: req.Envs,
|
||||||
Timeout: req.Timeout,
|
Timeout: req.Timeout,
|
||||||
Language: req.Language,
|
Languages: req.Languages,
|
||||||
LangVersion: req.LangVersion,
|
|
||||||
UseMise: req.UseMise,
|
UseMise: req.UseMise,
|
||||||
}, stdout, stderr, hooks)
|
}, stdout, stderr, hooks)
|
||||||
},
|
},
|
||||||
@@ -317,7 +315,7 @@ func (s *Scheduler) executeTask(req *ExecutionRequest) (*ExecutionResult, error)
|
|||||||
|
|
||||||
finalCommand := req.Command
|
finalCommand := req.Command
|
||||||
if req.UseMise {
|
if req.UseMise {
|
||||||
finalCommand = BuildLanguageCommand(req.Command, req.Language, req.LangVersion)
|
finalCommand = BuildLanguageCommand(req.Command, req.Languages)
|
||||||
}
|
}
|
||||||
s.logger.Infof("[Scheduler] 实际执行命令: %s", finalCommand)
|
s.logger.Infof("[Scheduler] 实际执行命令: %s", finalCommand)
|
||||||
|
|
||||||
|
|||||||
@@ -59,8 +59,7 @@ type AgentTask struct {
|
|||||||
Timeout int `json:"timeout"`
|
Timeout int `json:"timeout"`
|
||||||
WorkDir string `json:"work_dir"`
|
WorkDir string `json:"work_dir"`
|
||||||
Envs string `json:"envs"`
|
Envs string `json:"envs"`
|
||||||
Language string `json:"language"`
|
Languages []map[string]string `json:"languages"`
|
||||||
LangVersion string `json:"lang_version"`
|
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,7 @@ type Task struct {
|
|||||||
WorkDir string `json:"work_dir" gorm:"size:255;default:''"` // 工作目录,为空则使用 scripts 目录
|
WorkDir string `json:"work_dir" gorm:"size:255;default:''"` // 工作目录,为空则使用 scripts 目录
|
||||||
CleanConfig string `json:"clean_config" gorm:"size:255;default:''"` // 清理配置 JSON
|
CleanConfig string `json:"clean_config" gorm:"size:255;default:''"` // 清理配置 JSON
|
||||||
Envs string `json:"envs" gorm:"size:255;default:''"` // 环境变量ID列表,逗号分隔
|
Envs string `json:"envs" gorm:"size:255;default:''"` // 环境变量ID列表,逗号分隔
|
||||||
Language string `json:"language" gorm:"size:50;default:''"` // 针对本地任务的语言名称 (mise)
|
Languages []map[string]string `json:"languages" gorm:"serializer:json;type:text"` // 针对本地任务的语言配置列表
|
||||||
LangVersion string `json:"lang_version" gorm:"size:50;default:''"` // 针对本地任务的语言版本 (mise)
|
|
||||||
AgentID *uint `json:"agent_id" gorm:"index"` // Agent ID,为空表示本地执行
|
AgentID *uint `json:"agent_id" gorm:"index"` // Agent ID,为空表示本地执行
|
||||||
Enabled bool `json:"enabled" gorm:"default:true"`
|
Enabled bool `json:"enabled" gorm:"default:true"`
|
||||||
RunningGo string `json:"running_go" gorm:"type:text"` // 正在运行的 go routine id 数组 (JSON)
|
RunningGo string `json:"running_go" gorm:"type:text"` // 正在运行的 go routine id 数组 (JSON)
|
||||||
@@ -84,12 +83,8 @@ func (t *Task) GetEnvs() string {
|
|||||||
return t.Envs
|
return t.Envs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) GetLanguage() string {
|
func (t *Task) GetLanguages() []map[string]string {
|
||||||
return t.Language
|
return t.Languages
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Task) GetLangVersion() string {
|
|
||||||
return t.LangVersion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) GetUseMise() bool {
|
func (t *Task) GetUseMise() bool {
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ type TaskVO struct {
|
|||||||
WorkDir string `json:"work_dir"`
|
WorkDir string `json:"work_dir"`
|
||||||
CleanConfig string `json:"clean_config"`
|
CleanConfig string `json:"clean_config"`
|
||||||
Envs string `json:"envs"`
|
Envs string `json:"envs"`
|
||||||
Language string `json:"language"`
|
Languages []map[string]string `json:"languages"`
|
||||||
LangVersion string `json:"lang_version"`
|
|
||||||
AgentID *uint `json:"agent_id"`
|
AgentID *uint `json:"agent_id"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
LastRun *models.LocalTime `json:"last_run"`
|
LastRun *models.LocalTime `json:"last_run"`
|
||||||
@@ -43,8 +42,7 @@ func ToTaskVO(task *models.Task) *TaskVO {
|
|||||||
WorkDir: task.WorkDir,
|
WorkDir: task.WorkDir,
|
||||||
CleanConfig: task.CleanConfig,
|
CleanConfig: task.CleanConfig,
|
||||||
Envs: task.Envs,
|
Envs: task.Envs,
|
||||||
Language: task.Language,
|
Languages: task.Languages,
|
||||||
LangVersion: task.LangVersion,
|
|
||||||
AgentID: task.AgentID,
|
AgentID: task.AgentID,
|
||||||
Enabled: task.Enabled,
|
Enabled: task.Enabled,
|
||||||
LastRun: task.LastRun,
|
LastRun: task.LastRun,
|
||||||
|
|||||||
@@ -318,8 +318,7 @@ func (s *AgentService) GetTasks(agentID uint) []models.AgentTask {
|
|||||||
Timeout: task.Timeout,
|
Timeout: task.Timeout,
|
||||||
WorkDir: task.WorkDir,
|
WorkDir: task.WorkDir,
|
||||||
Envs: envVarsStr, // 传递 "KEY1=VALUE1,KEY2=VALUE2" 格式
|
Envs: envVarsStr, // 传递 "KEY1=VALUE1,KEY2=VALUE2" 格式
|
||||||
Language: task.Language,
|
Languages: task.Languages,
|
||||||
LangVersion: task.LangVersion,
|
|
||||||
Enabled: task.Enabled,
|
Enabled: task.Enabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -356,8 +356,7 @@ func (es *ExecutorService) ExecuteDispatcher(ctx context.Context, req *executor.
|
|||||||
WorkDir: req.WorkDir,
|
WorkDir: req.WorkDir,
|
||||||
Envs: req.Envs,
|
Envs: req.Envs,
|
||||||
Timeout: req.Timeout,
|
Timeout: req.Timeout,
|
||||||
Language: task.Language,
|
Languages: task.Languages,
|
||||||
LangVersion: task.LangVersion,
|
|
||||||
UseMise: true, // 本地业务任务启用 mise
|
UseMise: true, // 本地业务任务启用 mise
|
||||||
}, stdout, stderr, hooks)
|
}, stdout, stderr, hooks)
|
||||||
}
|
}
|
||||||
@@ -476,6 +475,8 @@ func (es *ExecutorService) ExecuteTask(taskID int, extraEnvs []string) *executor
|
|||||||
WorkDir: task.WorkDir,
|
WorkDir: task.WorkDir,
|
||||||
Envs: envs,
|
Envs: envs,
|
||||||
Timeout: task.Timeout,
|
Timeout: task.Timeout,
|
||||||
|
Languages: task.Languages,
|
||||||
|
UseMise: task.UseMise(),
|
||||||
Type: executor.TaskTypeManual,
|
Type: executor.TaskTypeManual,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func NewTaskService() *TaskService {
|
|||||||
return &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 == "" {
|
if taskType == "" {
|
||||||
taskType = "task"
|
taskType = "task"
|
||||||
}
|
}
|
||||||
@@ -25,8 +25,7 @@ func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, w
|
|||||||
WorkDir: workDir,
|
WorkDir: workDir,
|
||||||
CleanConfig: cleanConfig,
|
CleanConfig: cleanConfig,
|
||||||
Envs: envs,
|
Envs: envs,
|
||||||
Language: language,
|
Languages: languages,
|
||||||
LangVersion: langVersion,
|
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
}
|
}
|
||||||
@@ -67,7 +66,7 @@ func (ts *TaskService) GetTaskByID(id int) *models.Task {
|
|||||||
return &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
|
var task models.Task
|
||||||
if err := database.DB.First(&task, id).Error; err != nil {
|
if err := database.DB.First(&task, id).Error; err != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -81,8 +80,7 @@ func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeou
|
|||||||
task.Envs = envs
|
task.Envs = envs
|
||||||
task.Enabled = enabled
|
task.Enabled = enabled
|
||||||
task.AgentID = agentID
|
task.AgentID = agentID
|
||||||
task.Language = language
|
task.Languages = languages
|
||||||
task.LangVersion = langVersion
|
|
||||||
if taskType != "" {
|
if taskType != "" {
|
||||||
task.Type = taskType
|
task.Type = taskType
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,8 +271,7 @@ export interface Task {
|
|||||||
work_dir: string
|
work_dir: string
|
||||||
clean_config: string
|
clean_config: string
|
||||||
envs: string
|
envs: string
|
||||||
language: string
|
languages: { name: string; version: string }[]
|
||||||
lang_version: string
|
|
||||||
agent_id: number | null
|
agent_id: number | null
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
last_run: string
|
last_run: string
|
||||||
|
|||||||
@@ -89,13 +89,11 @@ const onlineAgents = computed(() => {
|
|||||||
// 语言环境相关
|
// 语言环境相关
|
||||||
const installedLangs = ref<MiseLanguage[]>([])
|
const installedLangs = ref<MiseLanguage[]>([])
|
||||||
const loadingLangs = ref(false)
|
const loadingLangs = ref(false)
|
||||||
|
const selectedLangs = ref<{ name: string; version: string; availableVersions: string[] }[]>([])
|
||||||
|
|
||||||
const availablePlugins = ref<string[]>([])
|
const availablePlugins = ref<string[]>([])
|
||||||
const pluginSearch = ref('')
|
const pluginSearch = ref('')
|
||||||
const openPluginPopover = ref(false)
|
|
||||||
|
|
||||||
const availableVersions = ref<string[]>([])
|
|
||||||
const versionSearch = ref('')
|
const versionSearch = ref('')
|
||||||
const openVersionPopover = ref(false)
|
|
||||||
|
|
||||||
const filteredPlugins = computed(() => {
|
const filteredPlugins = computed(() => {
|
||||||
if (!pluginSearch.value) return availablePlugins.value
|
if (!pluginSearch.value) return availablePlugins.value
|
||||||
@@ -103,11 +101,11 @@ const filteredPlugins = computed(() => {
|
|||||||
return availablePlugins.value.filter(p => p.toLowerCase().includes(s))
|
return availablePlugins.value.filter(p => p.toLowerCase().includes(s))
|
||||||
})
|
})
|
||||||
|
|
||||||
const filteredVersions = computed(() => {
|
function getFilteredVersions(versions: string[]) {
|
||||||
if (!versionSearch.value) return availableVersions.value
|
if (!versionSearch.value) return versions
|
||||||
const s = versionSearch.value.toLowerCase()
|
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() {
|
async function fetchInstalledLangs() {
|
||||||
loadingLangs.value = true
|
loadingLangs.value = true
|
||||||
@@ -163,22 +161,32 @@ function getLangIcon(plugin: string) {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => form.value.language, (newVal) => {
|
function updateAvailableVersions(lang: { name: string; version: string; availableVersions: string[] }) {
|
||||||
if (newVal) {
|
if (lang.name) {
|
||||||
availableVersions.value = installedLangs.value
|
lang.availableVersions = installedLangs.value
|
||||||
.filter(l => l.plugin === newVal)
|
.filter(l => l.plugin === lang.name)
|
||||||
.map(l => l.version)
|
.map(l => l.version)
|
||||||
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))
|
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))
|
||||||
|
|
||||||
// 如果当前选中的版本不在可用列表中,且列表不为空,则自动选中第一个(通常是最新版)
|
|
||||||
if (form.value.lang_version && !availableVersions.value.includes(form.value.lang_version)) {
|
|
||||||
// 保持原样,可能还没加载完
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
availableVersions.value = []
|
lang.availableVersions = []
|
||||||
form.value.lang_version = ''
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => {
|
watch(() => props.open, async (val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
@@ -233,6 +241,16 @@ watch(() => props.open, async (val) => {
|
|||||||
} else {
|
} else {
|
||||||
selectedEnvIds.value = []
|
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 和工作目录
|
// 解析 Agent 和工作目录
|
||||||
const agentId = props.task?.agent_id ? String(props.task.agent_id) : 'local'
|
const agentId = props.task?.agent_id ? String(props.task.agent_id) : 'local'
|
||||||
selectedAgentId.value = agentId
|
selectedAgentId.value = agentId
|
||||||
@@ -245,13 +263,10 @@ watch(() => props.open, async (val) => {
|
|||||||
await loadData()
|
await loadData()
|
||||||
if (selectedAgentId.value === 'local') {
|
if (selectedAgentId.value === 'local') {
|
||||||
await fetchInstalledLangs()
|
await fetchInstalledLangs()
|
||||||
// 如果已选择语言(编辑模式),手动触发一次版本更新
|
// 更新所有语言的可用版本
|
||||||
if (form.value.language) {
|
selectedLangs.value.forEach(lang => {
|
||||||
availableVersions.value = installedLangs.value
|
updateAvailableVersions(lang)
|
||||||
.filter(l => l.plugin === form.value.language)
|
})
|
||||||
.map(l => l.version)
|
|
||||||
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -285,6 +300,12 @@ async function save() {
|
|||||||
form.value.type = 'task'
|
form.value.type = 'task'
|
||||||
form.value.agent_id = selectedAgentId.value === 'local' ? null : Number(selectedAgentId.value)
|
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 字段被正确保存
|
// 保存配置 - 确保 concurrency 字段被正确保存
|
||||||
let config: Record<string, any> = {}
|
let config: Record<string, any> = {}
|
||||||
|
|
||||||
@@ -365,19 +386,19 @@ async function save() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-3">
|
<div class="grid grid-cols-1 sm:grid-cols-4 items-start gap-2 sm:gap-3">
|
||||||
<Label class="sm:text-right text-sm font-medium">语言环境</Label>
|
<Label class="sm:text-right text-sm font-medium pt-2">语言环境</Label>
|
||||||
<div class="sm:col-span-3 flex gap-2">
|
<div class="sm:col-span-3 space-y-2">
|
||||||
<Popover v-model:open="openPluginPopover">
|
<div v-for="(clang, idx) in selectedLangs" :key="idx" class="flex gap-2">
|
||||||
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button variant="outline" role="combobox" :aria-expanded="openPluginPopover"
|
<Button variant="outline" role="combobox" class="justify-between flex-1 h-8 text-sm font-normal">
|
||||||
class="justify-between flex-1 h-8 text-sm font-normal">
|
|
||||||
<div class="flex items-center gap-2 truncate">
|
<div class="flex items-center gap-2 truncate">
|
||||||
<div v-if="form.language && getLangIcon(form.language)"
|
<div v-if="clang.name && getLangIcon(clang.name)"
|
||||||
class="w-4 h-4 shrink-0 rounded-sm bg-white p-0.5 border">
|
class="w-4 h-4 shrink-0 rounded-sm bg-white p-0.5 border">
|
||||||
<img :src="getLangIcon(form.language)" class="w-full h-full object-contain" />
|
<img :src="getLangIcon(clang.name)" class="w-full h-full object-contain" />
|
||||||
</div>
|
</div>
|
||||||
<span>{{ form.language || "选择环境..." }}</span>
|
<span>{{ clang.name || "选择环境..." }}</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronsUpDown class="ml-2 h-3.5 w-3.5 shrink-0 opacity-50" />
|
<ChevronsUpDown class="ml-2 h-3.5 w-3.5 shrink-0 opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -398,8 +419,7 @@ async function save() {
|
|||||||
class="py-4 text-center text-xs text-muted-foreground">
|
class="py-4 text-center text-xs text-muted-foreground">
|
||||||
未找到已安装语言
|
未找到已安装语言
|
||||||
</div>
|
</div>
|
||||||
<button v-else v-for="p in filteredPlugins" :key="p"
|
<button v-else v-for="p in filteredPlugins" :key="p" @click="updateLangName(idx, p)"
|
||||||
@click="() => { form.language = p; openPluginPopover = false }"
|
|
||||||
class="w-full flex items-center px-2 py-1.5 text-xs rounded-sm hover:bg-muted text-left transition-colors group">
|
class="w-full flex items-center px-2 py-1.5 text-xs rounded-sm hover:bg-muted text-left transition-colors group">
|
||||||
<div class="mr-2 h-4 w-4 shrink-0 flex items-center justify-center relative">
|
<div class="mr-2 h-4 w-4 shrink-0 flex items-center justify-center relative">
|
||||||
<div v-if="getLangIcon(p)"
|
<div v-if="getLangIcon(p)"
|
||||||
@@ -410,21 +430,21 @@ async function save() {
|
|||||||
class="w-full h-full flex items-center justify-center bg-primary/10 rounded-sm text-[8px] font-bold uppercase border">
|
class="w-full h-full flex items-center justify-center bg-primary/10 rounded-sm text-[8px] font-bold uppercase border">
|
||||||
{{ p.substring(0, 2) }}
|
{{ p.substring(0, 2) }}
|
||||||
</div>
|
</div>
|
||||||
<Check v-if="form.language === p"
|
<Check v-if="clang.name === p"
|
||||||
class="absolute -right-2 -top-1 h-3 w-3 text-primary bg-background rounded-full border shadow-sm" />
|
class="absolute -right-2 -top-1 h-3 w-3 text-primary bg-background rounded-full border shadow-sm" />
|
||||||
</div>
|
</div>
|
||||||
<span :class="{ 'font-bold text-primary': form.language === p }">{{ p }}</span>
|
<span :class="{ 'font-bold text-primary': clang.name === p }">{{ p }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<Popover v-model:open="openVersionPopover">
|
<Popover>
|
||||||
<PopoverTrigger asChild :disabled="!form.language">
|
<PopoverTrigger asChild :disabled="!clang.name">
|
||||||
<Button variant="outline" role="combobox" :aria-expanded="openVersionPopover"
|
<Button variant="outline" role="combobox" class="justify-between w-32 h-8 text-sm font-normal"
|
||||||
class="justify-between w-32 h-8 text-sm font-normal" :disabled="!form.language">
|
:disabled="!clang.name">
|
||||||
<span class="truncate">{{ form.lang_version || "选择版本..." }}</span>
|
<span class="truncate">{{ clang.version || "选择版本..." }}</span>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ChevronsUpDown class="h-3.5 w-3.5 shrink-0 opacity-50" />
|
<ChevronsUpDown class="h-3.5 w-3.5 shrink-0 opacity-50" />
|
||||||
</div>
|
</div>
|
||||||
@@ -439,19 +459,31 @@ async function save() {
|
|||||||
</div>
|
</div>
|
||||||
<ScrollArea class="h-48">
|
<ScrollArea class="h-48">
|
||||||
<div class="p-1">
|
<div class="p-1">
|
||||||
<div v-if="filteredVersions.length === 0" class="py-4 text-center text-xs text-muted-foreground">
|
<div v-if="getFilteredVersions(clang.availableVersions).length === 0"
|
||||||
|
class="py-4 text-center text-xs text-muted-foreground">
|
||||||
无可用版本
|
无可用版本
|
||||||
</div>
|
</div>
|
||||||
<button v-else v-for="v in filteredVersions" :key="v"
|
<button v-else v-for="v in getFilteredVersions(clang.availableVersions)" :key="v"
|
||||||
@click="() => { form.lang_version = v; openVersionPopover = false }"
|
@click="clang.version = v"
|
||||||
class="w-full flex items-center px-2 py-1.5 text-xs rounded-sm hover:bg-muted text-left transition-colors">
|
class="w-full flex items-center px-2 py-1.5 text-xs rounded-sm hover:bg-muted text-left transition-colors">
|
||||||
<Check :class="cn('mr-2 h-3 w-3', form.lang_version === v ? 'opacity-100' : 'opacity-0')" />
|
<Check :class="cn('mr-2 h-3 w-3', clang.version === v ? 'opacity-100' : 'opacity-0')" />
|
||||||
<span class="truncate">{{ v }}</span>
|
<span class="truncate">{{ v }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
|
<Button variant="ghost" size="icon" class="h-8 w-8 text-muted-foreground hover:text-destructive"
|
||||||
|
@click="removeLang(idx)">
|
||||||
|
<X class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant="outline" size="sm" class="w-full h-8 text-xs border-dashed text-muted-foreground"
|
||||||
|
@click="addLang">
|
||||||
|
<Plus class="h-3.5 w-3.5 mr-1" /> 添加语言环境
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user