feat: add startup task and fix backup import
This commit is contained in:
@@ -78,6 +78,10 @@ const (
|
|||||||
TaskTypeNormal = "task"
|
TaskTypeNormal = "task"
|
||||||
TaskTypeRepo = "repo"
|
TaskTypeRepo = "repo"
|
||||||
|
|
||||||
|
// 触发类型
|
||||||
|
TriggerTypeCron = "cron"
|
||||||
|
TriggerTypeBaihuStartup = "baihu_startup"
|
||||||
|
|
||||||
// Agent 状态
|
// Agent 状态
|
||||||
AgentStatusOnline = "online"
|
AgentStatusOnline = "online"
|
||||||
AgentStatusOffline = "offline"
|
AgentStatusOffline = "offline"
|
||||||
|
|||||||
@@ -56,13 +56,14 @@ func (tc *TaskController) CreateTask(c *gin.Context) {
|
|||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Config string `json:"config"`
|
Config string `json:"config"`
|
||||||
Schedule string `json:"schedule" binding:"required"`
|
Schedule string `json:"schedule"`
|
||||||
Timeout int `json:"timeout"`
|
Timeout int `json:"timeout"`
|
||||||
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"`
|
||||||
Languages []map[string]string `json:"languages"`
|
Languages []map[string]string `json:"languages"`
|
||||||
AgentID *uint `json:"agent_id"`
|
AgentID *uint `json:"agent_id"`
|
||||||
|
TriggerType string `json:"trigger_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
@@ -76,9 +77,11 @@ func (tc *TaskController) CreateTask(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tc.executorService.ValidateCron(req.Schedule); err != nil {
|
if req.Schedule != "" {
|
||||||
utils.BadRequest(c, "无效的cron表达式: "+err.Error())
|
if err := tc.executorService.ValidateCron(req.Schedule); err != nil {
|
||||||
return
|
utils.BadRequest(c, "无效的cron表达式: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换为绝对路径(Agent 任务保持原样)
|
// 转换为绝对路径(Agent 任务保持原样)
|
||||||
@@ -87,7 +90,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.Languages)
|
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, req.TriggerType)
|
||||||
|
|
||||||
// 如果是 Agent 任务,通知 Agent;否则添加到本地 cron
|
// 如果是 Agent 任务,通知 Agent;否则添加到本地 cron
|
||||||
if task.AgentID != nil && *task.AgentID > 0 {
|
if task.AgentID != nil && *task.AgentID > 0 {
|
||||||
@@ -159,6 +162,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) {
|
|||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Languages []map[string]string `json:"languages"`
|
Languages []map[string]string `json:"languages"`
|
||||||
AgentID *uint `json:"agent_id"`
|
AgentID *uint `json:"agent_id"`
|
||||||
|
TriggerType string `json:"trigger_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
@@ -179,7 +183,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.Languages)
|
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, req.TriggerType)
|
||||||
if task == nil {
|
if task == nil {
|
||||||
utils.NotFound(c, "任务不存在")
|
utils.NotFound(c, "任务不存在")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type Task struct {
|
|||||||
Name string `json:"name" gorm:"size:255;not null"`
|
Name string `json:"name" gorm:"size:255;not null"`
|
||||||
Command string `json:"command" gorm:"type:text"` // 普通任务的命令
|
Command string `json:"command" gorm:"type:text"` // 普通任务的命令
|
||||||
Type string `json:"type" gorm:"size:20;default:'task'"` // 任务类型: constant.TaskTypeNormal, constant.TaskTypeRepo
|
Type string `json:"type" gorm:"size:20;default:'task'"` // 任务类型: constant.TaskTypeNormal, constant.TaskTypeRepo
|
||||||
|
TriggerType string `json:"trigger_type" gorm:"size:25;default:'cron'"` // 触发类型: constant.TriggerTypeCron, constant.TriggerTypeBaihuStartup
|
||||||
Config string `json:"config" gorm:"type:text"` // 配置 JSON(仓库同步配置等)
|
Config string `json:"config" gorm:"type:text"` // 配置 JSON(仓库同步配置等)
|
||||||
Schedule string `json:"schedule" gorm:"size:100"` // cron 表达式
|
Schedule string `json:"schedule" gorm:"size:100"` // cron 表达式
|
||||||
Timeout int `json:"timeout" gorm:"default:30"` // 超时时间(分钟),默认30分钟
|
Timeout int `json:"timeout" gorm:"default:30"` // 超时时间(分钟),默认30分钟
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type TaskVO struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
TriggerType string `json:"trigger_type"`
|
||||||
Config string `json:"config"`
|
Config string `json:"config"`
|
||||||
Schedule string `json:"schedule"`
|
Schedule string `json:"schedule"`
|
||||||
Timeout int `json:"timeout"`
|
Timeout int `json:"timeout"`
|
||||||
@@ -36,6 +37,7 @@ func ToTaskVO(task *models.Task) *TaskVO {
|
|||||||
Name: task.Name,
|
Name: task.Name,
|
||||||
Command: task.Command,
|
Command: task.Command,
|
||||||
Type: task.Type,
|
Type: task.Type,
|
||||||
|
TriggerType: task.TriggerType,
|
||||||
Config: task.Config,
|
Config: task.Config,
|
||||||
Schedule: task.Schedule,
|
Schedule: task.Schedule,
|
||||||
Timeout: task.Timeout,
|
Timeout: task.Timeout,
|
||||||
|
|||||||
@@ -227,6 +227,32 @@ func (s *BackupService) Restore(zipPath string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func restoreStreamBatch[T any](tx *gorm.DB, decoder *json.Decoder) error {
|
||||||
|
batchSize := 1000
|
||||||
|
var batch []*T
|
||||||
|
|
||||||
|
for decoder.More() {
|
||||||
|
var m T
|
||||||
|
if err := decoder.Decode(&m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
batch = append(batch, &m)
|
||||||
|
|
||||||
|
if len(batch) >= batchSize {
|
||||||
|
if err := tx.CreateInBatches(batch, batchSize).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
batch = nil // reset batch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(batch) > 0 {
|
||||||
|
return tx.CreateInBatches(batch, len(batch)).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *BackupService) restoreFromZipFile(tx *gorm.DB, f *zip.File, filename string) error {
|
func (s *BackupService) restoreFromZipFile(tx *gorm.DB, f *zip.File, filename string) error {
|
||||||
rc, err := f.Open()
|
rc, err := f.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -254,60 +280,30 @@ func (s *BackupService) restoreFromZipFile(tx *gorm.DB, f *zip.File, filename st
|
|||||||
return fmt.Errorf("invalid json format: expected %s", filename)
|
return fmt.Errorf("invalid json format: expected %s", filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
batchSize := 1000
|
switch filename {
|
||||||
var batch []any
|
case "tasks.json":
|
||||||
|
return restoreStreamBatch[models.Task](tx, decoder)
|
||||||
// 根据文件名确定模型类型
|
case "task_logs.json":
|
||||||
getModel := func() any {
|
return restoreStreamBatch[models.TaskLog](tx, decoder)
|
||||||
switch filename {
|
case "envs.json":
|
||||||
case "tasks.json":
|
return restoreStreamBatch[models.EnvironmentVariable](tx, decoder)
|
||||||
return &models.Task{}
|
case "scripts.json":
|
||||||
case "task_logs.json":
|
return restoreStreamBatch[models.Script](tx, decoder)
|
||||||
return &models.TaskLog{}
|
case "send_stats.json":
|
||||||
case "envs.json":
|
return restoreStreamBatch[models.SendStats](tx, decoder)
|
||||||
return &models.EnvironmentVariable{}
|
case "login_logs.json":
|
||||||
case "scripts.json":
|
return restoreStreamBatch[models.LoginLog](tx, decoder)
|
||||||
return &models.Script{}
|
case "agents.json":
|
||||||
case "send_stats.json":
|
return restoreStreamBatch[models.Agent](tx, decoder)
|
||||||
return &models.SendStats{}
|
case "tokens.json":
|
||||||
case "login_logs.json":
|
return restoreStreamBatch[models.AgentToken](tx, decoder)
|
||||||
return &models.LoginLog{}
|
case "languages.json":
|
||||||
case "agents.json":
|
return restoreStreamBatch[models.Language](tx, decoder)
|
||||||
return &models.Agent{}
|
case "deps.json":
|
||||||
case "tokens.json":
|
return restoreStreamBatch[models.Dependency](tx, decoder)
|
||||||
return &models.AgentToken{}
|
default:
|
||||||
case "languages.json":
|
return nil
|
||||||
return &models.Language{}
|
|
||||||
case "deps.json":
|
|
||||||
return &models.Dependency{}
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for decoder.More() {
|
|
||||||
m := getModel()
|
|
||||||
if m == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err := decoder.Decode(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
batch = append(batch, m)
|
|
||||||
|
|
||||||
if len(batch) >= batchSize {
|
|
||||||
if err := tx.CreateInBatches(batch, batchSize).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
batch = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(batch) > 0 {
|
|
||||||
return tx.CreateInBatches(batch, batchSize).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// insertRecords, restoreFromData 方法已合并入 restoreFromZipFile,此处删除冗余方法
|
// insertRecords, restoreFromData 方法已合并入 restoreFromZipFile,此处删除冗余方法
|
||||||
|
|||||||
@@ -416,6 +416,10 @@ func (es *ExecutorService) StopCron() {
|
|||||||
|
|
||||||
// AddCronTask 添加计划任务
|
// AddCronTask 添加计划任务
|
||||||
func (es *ExecutorService) AddCronTask(task *models.Task) error {
|
func (es *ExecutorService) AddCronTask(task *models.Task) error {
|
||||||
|
if task.TriggerType != constant.TriggerTypeCron {
|
||||||
|
es.RemoveCronTask(task.ID) // 如果不是cron类型,确保从调度器移除
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return es.cronManager.AddTask(task)
|
return es.cronManager.AddTask(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,8 +443,19 @@ func (es *ExecutorService) loadCronTasks() {
|
|||||||
tasks := es.taskService.GetTasks()
|
tasks := es.taskService.GetTasks()
|
||||||
count := 0
|
count := 0
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
// 只调度本地任务(agent_id 为空或 0)
|
if !task.Enabled {
|
||||||
if task.Enabled && (task.AgentID == nil || *task.AgentID == 0) {
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if task.TriggerType == constant.TriggerTypeBaihuStartup {
|
||||||
|
go func(t models.Task) {
|
||||||
|
// 延迟一点时间再触发,确保系统完全启动
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
logger.Infof("[Executor] 触发开机服务启动任务 #%d: %s", t.ID, t.Name)
|
||||||
|
es.ExecuteTask(int(t.ID), nil)
|
||||||
|
}(task)
|
||||||
|
} else if task.TriggerType == constant.TriggerTypeCron && task.Schedule != "" && (task.AgentID == nil || *task.AgentID == 0) {
|
||||||
|
// 只调度本地任务(agent_id 为空或 0)的定时任务
|
||||||
err := es.cronManager.AddTask(&task)
|
err := es.cronManager.AddTask(&task)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package tasks
|
package tasks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/engigu/baihu-panel/internal/constant"
|
||||||
"github.com/engigu/baihu-panel/internal/database"
|
"github.com/engigu/baihu-panel/internal/database"
|
||||||
"github.com/engigu/baihu-panel/internal/models"
|
"github.com/engigu/baihu-panel/internal/models"
|
||||||
)
|
)
|
||||||
@@ -11,14 +12,18 @@ 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, languages []map[string]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, triggerType string) *models.Task {
|
||||||
if taskType == "" {
|
if taskType == "" {
|
||||||
taskType = "task"
|
taskType = "task"
|
||||||
}
|
}
|
||||||
|
if triggerType == "" {
|
||||||
|
triggerType = constant.TriggerTypeCron
|
||||||
|
}
|
||||||
task := &models.Task{
|
task := &models.Task{
|
||||||
Name: name,
|
Name: name,
|
||||||
Command: command,
|
Command: command,
|
||||||
Type: taskType,
|
Type: taskType,
|
||||||
|
TriggerType: triggerType,
|
||||||
Config: config,
|
Config: config,
|
||||||
Schedule: schedule,
|
Schedule: schedule,
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
@@ -29,6 +34,9 @@ func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, w
|
|||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
}
|
}
|
||||||
|
if triggerType != constant.TriggerTypeCron {
|
||||||
|
task.NextRun = nil
|
||||||
|
}
|
||||||
database.DB.Create(task)
|
database.DB.Create(task)
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
@@ -66,7 +74,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, languages []map[string]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, triggerType 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
|
||||||
@@ -84,6 +92,12 @@ func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeou
|
|||||||
if taskType != "" {
|
if taskType != "" {
|
||||||
task.Type = taskType
|
task.Type = taskType
|
||||||
}
|
}
|
||||||
|
if triggerType != "" {
|
||||||
|
task.TriggerType = triggerType
|
||||||
|
}
|
||||||
|
if task.TriggerType != constant.TriggerTypeCron {
|
||||||
|
task.NextRun = nil
|
||||||
|
}
|
||||||
task.Config = config
|
task.Config = config
|
||||||
database.DB.Save(&task)
|
database.DB.Save(&task)
|
||||||
return &task
|
return &task
|
||||||
|
|||||||
Vendored
@@ -266,6 +266,7 @@ export interface Task {
|
|||||||
name: string
|
name: string
|
||||||
command: string
|
command: string
|
||||||
type: string
|
type: string
|
||||||
|
trigger_type: string
|
||||||
config: string
|
config: string
|
||||||
schedule: string
|
schedule: string
|
||||||
timeout: number
|
timeout: number
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ export const TASK_TYPE = {
|
|||||||
REPO: 'repo',
|
REPO: 'repo',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
// 触发类型
|
||||||
|
export const TRIGGER_TYPE = {
|
||||||
|
CRON: 'cron',
|
||||||
|
BAIHU_STARTUP: 'baihu_startup',
|
||||||
|
} as const
|
||||||
|
|
||||||
// Agent 状态
|
// Agent 状态
|
||||||
export const AGENT_STATUS = {
|
export const AGENT_STATUS = {
|
||||||
ONLINE: 'online',
|
ONLINE: 'online',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import DirTreeSelect from '@/components/DirTreeSelect.vue'
|
|||||||
import { Plus, ChevronDown, X, Search, Check, ChevronsUpDown, Loader2, AlertCircle } from 'lucide-vue-next'
|
import { Plus, ChevronDown, X, Search, Check, ChevronsUpDown, Loader2, AlertCircle } from 'lucide-vue-next'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { api, type Task, type EnvVar, type Agent, type MiseLanguage } from '@/api'
|
import { api, type Task, type EnvVar, type Agent, type MiseLanguage } from '@/api'
|
||||||
|
import { TRIGGER_TYPE } from '@/constants'
|
||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -44,6 +45,7 @@ const allEnvVars = ref<EnvVar[]>([])
|
|||||||
const allAgents = ref<Agent[]>([])
|
const allAgents = ref<Agent[]>([])
|
||||||
const selectedEnvIds = ref<number[]>([])
|
const selectedEnvIds = ref<number[]>([])
|
||||||
const selectedAgentId = ref<string>('local')
|
const selectedAgentId = ref<string>('local')
|
||||||
|
const selectedTriggerType = ref<string>('cron')
|
||||||
const envSearchQuery = ref('')
|
const envSearchQuery = ref('')
|
||||||
// 为每个执行位置保存独立的工作目录配置
|
// 为每个执行位置保存独立的工作目录配置
|
||||||
const workDirCache = ref<Record<string, string>>({})
|
const workDirCache = ref<Record<string, string>>({})
|
||||||
@@ -254,6 +256,8 @@ watch(() => props.open, async (val) => {
|
|||||||
// 解析 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
|
||||||
|
// 解析触发类型
|
||||||
|
selectedTriggerType.value = props.task?.trigger_type || TRIGGER_TYPE.CRON
|
||||||
// 初始化工作目录缓存,将当前任务的工作目录保存到对应的执行位置
|
// 初始化工作目录缓存,将当前任务的工作目录保存到对应的执行位置
|
||||||
workDirCache.value = {
|
workDirCache.value = {
|
||||||
[agentId]: props.task?.work_dir || ''
|
[agentId]: props.task?.work_dir || ''
|
||||||
@@ -298,6 +302,7 @@ async function save() {
|
|||||||
form.value.clean_config = cleanConfig.value
|
form.value.clean_config = cleanConfig.value
|
||||||
form.value.envs = selectedEnvIds.value.join(',')
|
form.value.envs = selectedEnvIds.value.join(',')
|
||||||
form.value.type = 'task'
|
form.value.type = 'task'
|
||||||
|
form.value.trigger_type = selectedTriggerType.value
|
||||||
form.value.agent_id = selectedAgentId.value === 'local' ? null : Number(selectedAgentId.value)
|
form.value.agent_id = selectedAgentId.value === 'local' ? null : Number(selectedAgentId.value)
|
||||||
|
|
||||||
// 保存语言环境配置
|
// 保存语言环境配置
|
||||||
@@ -374,6 +379,21 @@ async function save() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-2 sm:gap-3">
|
||||||
|
<Label class="sm:text-right text-sm">触发类型</Label>
|
||||||
|
<div class="sm:col-span-3">
|
||||||
|
<Select v-model="selectedTriggerType">
|
||||||
|
<SelectTrigger class="h-8 text-sm">
|
||||||
|
<SelectValue placeholder="定时触发" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem :value="TRIGGER_TYPE.CRON">定时触发</SelectItem>
|
||||||
|
<SelectItem :value="TRIGGER_TYPE.BAIHU_STARTUP">服务启动时触发</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 本地任务语言版本配置 -->
|
<!-- 本地任务语言版本配置 -->
|
||||||
<template v-if="selectedAgentId === 'local'">
|
<template v-if="selectedAgentId === 'local'">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-start gap-2 sm:gap-3">
|
<div class="grid grid-cols-1 sm:grid-cols-4 items-start gap-2 sm:gap-3">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { api, type Task, type Agent } from '@/api'
|
|||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
import { useSiteSettings } from '@/composables/useSiteSettings'
|
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { TASK_TYPE, AGENT_STATUS } from '@/constants'
|
import { TASK_TYPE, AGENT_STATUS, TRIGGER_TYPE } from '@/constants'
|
||||||
import TextOverflow from '@/components/TextOverflow.vue'
|
import TextOverflow from '@/components/TextOverflow.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -261,8 +261,10 @@ watch(() => route.query.agent_id, (newVal) => {
|
|||||||
class="w-32 sm:flex-1 shrink-0 sm:shrink text-muted-foreground truncate text-xs bg-muted/40 px-2 py-1 rounded hidden sm:block">
|
class="w-32 sm:flex-1 shrink-0 sm:shrink text-muted-foreground truncate text-xs bg-muted/40 px-2 py-1 rounded hidden sm:block">
|
||||||
<TextOverflow :text="task.command" :title="task.type === TASK_TYPE.REPO ? '同步地址' : '执行命令'" />
|
<TextOverflow :text="task.command" :title="task.type === TASK_TYPE.REPO ? '同步地址' : '执行命令'" />
|
||||||
</code>
|
</code>
|
||||||
<code class="w-36 shrink-0 text-muted-foreground text-xs bg-muted/40 px-2 py-1 rounded hidden md:block">{{ task.schedule
|
<div class="w-36 shrink-0 hidden md:flex flex-col items-start justify-center gap-1 overflow-hidden">
|
||||||
}}</code>
|
<span v-if="task.trigger_type === TRIGGER_TYPE.BAIHU_STARTUP" class="text-[10px] leading-none bg-primary/10 text-primary px-1.5 py-1 rounded whitespace-nowrap border border-primary/20">服务启动时</span>
|
||||||
|
<code v-else-if="task.schedule" class="text-muted-foreground text-xs bg-muted/40 px-1.5 py-0.5 rounded truncate max-w-full" :title="task.schedule">{{ task.schedule }}</code>
|
||||||
|
</div>
|
||||||
<span class="w-40 shrink-0 text-muted-foreground text-xs hidden lg:block">{{ task.last_run || '-' }}</span>
|
<span class="w-40 shrink-0 text-muted-foreground text-xs hidden lg:block">{{ task.last_run || '-' }}</span>
|
||||||
<span class="w-40 shrink-0 text-muted-foreground text-xs hidden lg:block">{{ task.next_run || '-' }}</span>
|
<span class="w-40 shrink-0 text-muted-foreground text-xs hidden lg:block">{{ task.next_run || '-' }}</span>
|
||||||
<span class="w-8 sm:w-12 flex justify-center shrink-0 cursor-pointer group"
|
<span class="w-8 sm:w-12 flex justify-center shrink-0 cursor-pointer group"
|
||||||
|
|||||||
Reference in New Issue
Block a user