From 61d32857a732fb5f97f44f6935a78b67c63bec3c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E6=99=AF=E9=98=B3?= <1656488874@qq.com>
Date: Sat, 12 Oct 2019 07:16:11 +0800
Subject: [PATCH 01/11] =?UTF-8?q?fix=20worker=E6=97=A0=E6=B3=95=E8=8E=B7?=
=?UTF-8?q?=E5=8F=96=E5=88=B0=E5=BD=93=E5=89=8DNode=E7=9A=84=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/services/node.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/services/node.go b/backend/services/node.go
index 04cbc0ef..d3409ed2 100644
--- a/backend/services/node.go
+++ b/backend/services/node.go
@@ -227,7 +227,7 @@ func InitNodeService() error {
}
// 首次更新节点数据(注册到Redis)
- // UpdateNodeData()
+ UpdateNodeData()
// 获取当前节点
node, err := model.GetCurrentNode()
From a00fcee8014b60c06fed79089456a57ef7d1836f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E6=99=AF=E9=98=B3?= <1656488874@qq.com>
Date: Tue, 15 Oct 2019 17:23:03 +0800
Subject: [PATCH 02/11] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/services/node.go | 14 ++--
backend/services/task.go | 173 ++++++++++++++++++++++++---------------
2 files changed, 113 insertions(+), 74 deletions(-)
diff --git a/backend/services/node.go b/backend/services/node.go
index d3409ed2..8b1f998a 100644
--- a/backend/services/node.go
+++ b/backend/services/node.go
@@ -100,13 +100,13 @@ func handleNodeInfo(key string, data Data) {
defer s.Close()
// 同个key可能因为并发,被注册多次
- //var nodes []model.Node
- //_ = c.Find(bson.M{"key": key}).All(&nodes)
- //if nodes != nil && len(nodes) > 1 {
- // for _, node := range nodes {
- // _ = c.RemoveId(node.Id)
- // }
- //}
+ var nodes []model.Node
+ _ = c.Find(bson.M{"key": key}).All(&nodes)
+ if nodes != nil && len(nodes) > 1 {
+ for _, node := range nodes {
+ _ = c.RemoveId(node.Id)
+ }
+ }
var node model.Node
if err := c.Find(bson.M{"key": key}).One(&node); err != nil {
diff --git a/backend/services/task.go b/backend/services/task.go
index 12f0330e..b79dbe7a 100644
--- a/backend/services/task.go
+++ b/backend/services/task.go
@@ -100,10 +100,104 @@ func AssignTask(task model.Task) error {
return nil
}
+// 设置环境变量
+func SetEnv(cmd *exec.Cmd, envs []model.Env, taskId string, dataCol string) *exec.Cmd {
+ // 默认环境变量
+ cmd.Env = append(cmd.Env, "CRAWLAB_TASK_ID="+taskId)
+ cmd.Env = append(cmd.Env, "CRAWLAB_COLLECTION="+dataCol)
+ cmd.Env = append(cmd.Env, "PYTHONUNBUFFERED=0")
+ cmd.Env = append(cmd.Env, "PYTHONIOENCODING=utf-8")
+
+ //任务环境变量
+ for _, env := range envs {
+ cmd.Env = append(cmd.Env, env.Name+"="+env.Value)
+ }
+
+ // TODO 全局环境变量
+ return cmd
+}
+
+func SetLogConfig(cmd *exec.Cmd, path string) error {
+ fLog, err := os.Create(path)
+ if err != nil {
+ log.Errorf("create task log file error: %s", path)
+ debug.PrintStack()
+ return err
+ }
+ defer fLog.Close()
+ cmd.Stdout = fLog
+ cmd.Stderr = fLog
+ return nil
+}
+
+func FinishOrCancelTask(ch chan string, cmd *exec.Cmd, t model.Task) {
+ // 传入信号,此处阻塞
+ signal := <-ch
+ log.Infof("process received signal: %s", signal)
+
+ if signal == constants.TaskCancel && cmd.Process != nil {
+ // 取消进程
+ if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL); err != nil {
+ log.Errorf("process kill error: %s", err.Error())
+ debug.PrintStack()
+
+ t.Error = "kill process error: " + err.Error()
+ t.Status = constants.StatusError
+ } else {
+ t.Error = "user kill the process ..."
+ t.Status = constants.StatusCancelled
+ }
+ } else {
+ // 保存任务
+ t.Status = constants.StatusFinished
+ }
+
+ t.FinishTs = time.Now()
+ _ = t.Save()
+}
+
+func StartTaskProcess(cmd *exec.Cmd, t model.Task) error {
+ if err := cmd.Start(); err != nil {
+ log.Errorf("start spider error:{}", err.Error())
+ debug.PrintStack()
+
+ t.Error = "start task error: " + err.Error()
+ t.Status = constants.StatusError
+ t.FinishTs = time.Now()
+ _ = t.Save()
+ return err
+ }
+ return nil
+}
+
+func WaitTaskProcess(cmd *exec.Cmd, t model.Task) error {
+ if err := cmd.Wait(); err != nil {
+ log.Errorf("wait process finish error: %s", err.Error())
+ debug.PrintStack()
+
+ if exitError, ok := err.(*exec.ExitError); ok {
+ exitCode := exitError.ExitCode()
+ log.Errorf("exit error, exit code: %d", exitCode)
+
+ // 非kill 的错误类型
+ if exitCode != -1 {
+ // 非手动kill保存为错误状态
+ t.Error = err.Error()
+ t.FinishTs = time.Now()
+ t.Status = constants.StatusError
+ _ = t.Save()
+ }
+ }
+
+ return err
+ }
+ return nil
+}
+
// 执行shell命令
func ExecuteShellCmd(cmdStr string, cwd string, t model.Task, s model.Spider) (err error) {
- log.Infof("cwd: " + cwd)
- log.Infof("cmd: " + cmdStr)
+ log.Infof("cwd: %s", cwd)
+ log.Infof("cmd: %s", cmdStr)
// 生成执行命令
var cmd *exec.Cmd
@@ -116,84 +210,29 @@ func ExecuteShellCmd(cmdStr string, cwd string, t model.Task, s model.Spider) (e
// 工作目录
cmd.Dir = cwd
- // 指定stdout, stderr日志位置
- fLog, err := os.Create(t.LogPath)
- if err != nil {
- HandleTaskError(t, err)
+ // 日志配置
+ if err := SetLogConfig(cmd, t.LogPath); err != nil {
return err
}
- defer fLog.Close()
- cmd.Stdout = fLog
- cmd.Stderr = fLog
- // 添加默认环境变量
- cmd.Env = append(cmd.Env, "CRAWLAB_TASK_ID="+t.Id)
- cmd.Env = append(cmd.Env, "CRAWLAB_COLLECTION="+s.Col)
-
- // 添加任务环境变量
- for _, env := range s.Envs {
- cmd.Env = append(cmd.Env, env.Name+"="+env.Value)
- }
+ // 环境变量配置
+ cmd = SetEnv(cmd, s.Envs, t.Id, s.Col)
// 起一个goroutine来监控进程
ch := utils.TaskExecChanMap.ChanBlocked(t.Id)
- go func() {
- // 传入信号,此处阻塞
- signal := <-ch
- log.Infof("cancel process signal: %s", signal)
- if signal == constants.TaskCancel && cmd.Process != nil {
- // 取消进程
- if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL); err != nil {
- log.Errorf("process kill error: %s", err.Error())
- debug.PrintStack()
- }
- t.Status = constants.StatusCancelled
- t.Error = "user kill the process ..."
- } else {
- // 保存任务
- t.Status = constants.StatusFinished
- }
- t.FinishTs = time.Now()
- if err := t.Save(); err != nil {
- log.Infof("save task error: %s", err.Error())
- debug.PrintStack()
- return
- }
- }()
- // 在选择所有节点执行的时候,实际就是随机一个节点执行的,
+ go FinishOrCancelTask(ch, cmd, t)
+
+ // kill的时候,可以kill所有的子进程
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
- // 异步启动进程
- if err := cmd.Start(); err != nil {
- log.Errorf("start spider error:{}", err.Error())
- debug.PrintStack()
+ // 启动进程
+ if err := StartTaskProcess(cmd, t); err != nil {
return err
}
- // 保存pid到task
- t.Pid = cmd.Process.Pid
- if err := t.Save(); err != nil {
- log.Errorf("save task pid error: %s", err.Error())
- debug.PrintStack()
- return err
- }
// 同步等待进程完成
- if err := cmd.Wait(); err != nil {
- log.Errorf("wait process finish error: %s", err.Error())
- debug.PrintStack()
- if exitError, ok := err.(*exec.ExitError); ok {
- exitCode := exitError.ExitCode()
- log.Errorf("exit error, exit code: %d", exitCode)
- // 非kill 的错误类型
- if exitCode != -1 {
- // 非手动kill保存为错误状态
- t.Error = err.Error()
- t.FinishTs = time.Now()
- t.Status = constants.StatusError
- _ = t.Save()
- }
- }
+ if err := WaitTaskProcess(cmd, t); err != nil {
return err
}
ch <- constants.TaskFinish
From 20fdb5f2d3d282df8afa2b2b1e28d1d174cb0070 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E6=99=AF=E9=98=B3?= <1656488874@qq.com>
Date: Wed, 16 Oct 2019 09:45:50 +0800
Subject: [PATCH 03/11] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/services/task.go | 49 +++++++++++++---------------------------
1 file changed, 16 insertions(+), 33 deletions(-)
diff --git a/backend/services/task.go b/backend/services/task.go
index b79dbe7a..50b902cb 100644
--- a/backend/services/task.go
+++ b/backend/services/task.go
@@ -311,70 +311,53 @@ func ExecuteTask(id int) {
// 获取当前节点
node, err := model.GetCurrentNode()
if err != nil {
- log.Errorf(GetWorkerPrefix(id) + err.Error())
+ log.Errorf("execute task get current node error: %s", err.Error())
+ debug.PrintStack()
return
}
- // 公共队列
- queuePub := "tasks:public"
-
// 节点队列
queueCur := "tasks:node:" + node.Id.Hex()
-
// 节点队列任务
var msg string
- msg, err = database.RedisClient.LPop(queueCur)
- if msg != "" {
- log.Infof("queue cur: %s", msg)
- }
- if err != nil {
- if msg == "" {
- // 节点队列没有任务,获取公共队列任务
- msg, err = database.RedisClient.LPop(queuePub)
- if err != nil {
- if msg == "" {
- // 公共队列没有任务
- log.Debugf(GetWorkerPrefix(id) + "没有任务...")
- return
- } else {
- log.Errorf(GetWorkerPrefix(id) + err.Error())
- debug.PrintStack()
- return
- }
- }
- } else {
- log.Errorf(GetWorkerPrefix(id) + err.Error())
- debug.PrintStack()
- return
+ if msg, err = database.RedisClient.LPop(queueCur); err != nil {
+ log.Errorf("get current node task error: %s", err.Error())
+ // 节点队列没有任务,获取公共队列任务
+ queuePub := "tasks:public"
+ if msg, err = database.RedisClient.LPop(queuePub); err != nil {
+ log.Errorf("get public task error: %s", err.Error())
}
}
+ if msg == "" {
+ return
+ }
+
// 反序列化
tMsg := TaskMessage{}
if err := json.Unmarshal([]byte(msg), &tMsg); err != nil {
- log.Errorf(GetWorkerPrefix(id) + err.Error())
- debug.PrintStack()
+ log.Errorf("json string to struct error: %s", err.Error())
return
}
// 获取任务
t, err := model.GetTask(tMsg.Id)
if err != nil {
- log.Errorf(GetWorkerPrefix(id) + err.Error())
+ log.Errorf("execute task, get task error: %s", err.Error())
return
}
// 获取爬虫
spider, err := t.GetSpider()
if err != nil {
- log.Errorf(GetWorkerPrefix(id) + err.Error())
+ log.Errorf("execute task, get spider error: %s", err.Error())
return
}
// 创建日志目录
fileDir, err := MakeLogDir(t)
if err != nil {
- log.Errorf(GetWorkerPrefix(id) + err.Error())
+ log.Errorf("execute task, make log dir error: %s", err.Error())
return
}
From 5da64fd00bc3aa2d3744519d40461893e9f0f124 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E6=99=AF=E9=98=B3?= <1656488874@qq.com>
Date: Wed, 16 Oct 2019 10:11:00 +0800
Subject: [PATCH 04/11] =?UTF-8?q?fix=20=E4=B8=80=E4=BA=9B=E4=B8=9C?=
=?UTF-8?q?=E8=A5=BF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/services/task.go | 19 +++----------------
1 file changed, 3 insertions(+), 16 deletions(-)
diff --git a/backend/services/task.go b/backend/services/task.go
index 50b902cb..78c11f17 100644
--- a/backend/services/task.go
+++ b/backend/services/task.go
@@ -124,7 +124,6 @@ func SetLogConfig(cmd *exec.Cmd, path string) error {
debug.PrintStack()
return err
}
- defer fLog.Close()
cmd.Stdout = fLog
cmd.Stderr = fLog
return nil
@@ -247,6 +246,7 @@ func MakeLogDir(t model.Task) (fileDir string, err error) {
// 如果日志目录不存在,生成该目录
if !utils.Exists(fileDir) {
if err := os.MkdirAll(fileDir, 0777); err != nil {
+ log.Errorf("execute task, make log dir error: %s", err.Error())
debug.PrintStack()
return "", err
}
@@ -321,11 +321,9 @@ func ExecuteTask(id int) {
// 节点队列任务
var msg string
if msg, err = database.RedisClient.LPop(queueCur); err != nil {
- log.Errorf("get current node task error: %s", err.Error())
// 节点队列没有任务,获取公共队列任务
queuePub := "tasks:public"
if msg, err = database.RedisClient.LPop(queuePub); err != nil {
- log.Errorf("get public task error: %s", err.Error())
}
}
@@ -355,24 +353,13 @@ func ExecuteTask(id int) {
}
// 创建日志目录
- fileDir, err := MakeLogDir(t)
- if err != nil {
- log.Errorf("execute task, make log dir error: %s", err.Error())
+ var fileDir string
+ if fileDir, err = MakeLogDir(t); err != nil {
return
}
-
// 获取日志文件路径
t.LogPath = GetLogFilePaths(fileDir)
- // 创建日志目录文件夹
- fileStdoutDir := filepath.Dir(t.LogPath)
- if !utils.Exists(fileStdoutDir) {
- if err := os.MkdirAll(fileStdoutDir, os.ModePerm); err != nil {
- log.Errorf(GetWorkerPrefix(id) + err.Error())
- return
- }
- }
-
// 工作目录
cwd := filepath.Join(
viper.GetString("spider.path"),
From ecab6dfa032a67eb0598087e50bed8a5ca61bf8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E6=99=AF=E9=98=B3?= <1656488874@qq.com>
Date: Wed, 16 Oct 2019 11:26:15 +0800
Subject: [PATCH 05/11] =?UTF-8?q?fix=20=E6=97=B6=E5=8C=BA=E7=9A=84?=
=?UTF-8?q?=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/services/task.go | 2 ++
1 file changed, 2 insertions(+)
diff --git a/backend/services/task.go b/backend/services/task.go
index 78c11f17..5bcb54e8 100644
--- a/backend/services/task.go
+++ b/backend/services/task.go
@@ -107,6 +107,7 @@ func SetEnv(cmd *exec.Cmd, envs []model.Env, taskId string, dataCol string) *exe
cmd.Env = append(cmd.Env, "CRAWLAB_COLLECTION="+dataCol)
cmd.Env = append(cmd.Env, "PYTHONUNBUFFERED=0")
cmd.Env = append(cmd.Env, "PYTHONIOENCODING=utf-8")
+ cmd.Env = append(cmd.Env, "TZ=Asia/Shanghai")
//任务环境变量
for _, env := range envs {
@@ -203,6 +204,7 @@ func ExecuteShellCmd(cmdStr string, cwd string, t model.Task, s model.Spider) (e
if runtime.GOOS == constants.Windows {
cmd = exec.Command("cmd", "/C", cmdStr)
} else {
+ cmd = exec.Command("")
cmd = exec.Command("sh", "-c", cmdStr)
}
From 05136b0fac77541017665d798c8a6ef42edfb136 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E6=99=AF=E9=98=B3?= <1656488874@qq.com>
Date: Wed, 16 Oct 2019 15:27:12 +0800
Subject: [PATCH 06/11] =?UTF-8?q?fix=20=E5=AE=9A=E6=97=B6=E4=BB=BB?=
=?UTF-8?q?=E5=8A=A1=E5=8F=82=E6=95=B0=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/model/node.go | 14 +++++++++-----
backend/model/schedule.go | 31 ++++++++++++++++++++++++-------
backend/routes/schedule.go | 22 ++++++++++++----------
backend/services/schedule.go | 17 ++++++++++++++++-
4 files changed, 61 insertions(+), 23 deletions(-)
diff --git a/backend/model/node.go b/backend/model/node.go
index 1a1ebce5..2beb9e1c 100644
--- a/backend/model/node.go
+++ b/backend/model/node.go
@@ -4,6 +4,7 @@ import (
"crawlab/constants"
"crawlab/database"
"crawlab/services/register"
+ "errors"
"github.com/apex/log"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
@@ -158,16 +159,19 @@ func GetNodeList(filter interface{}) ([]Node, error) {
func GetNode(id bson.ObjectId) (Node, error) {
var node Node
+
if id.Hex() == "" {
- return node, nil
+ log.Infof("id is empty")
+ debug.PrintStack()
+ return node, errors.New("id is empty")
}
+
s, c := database.GetCol("nodes")
defer s.Close()
+
if err := c.FindId(id).One(&node); err != nil {
- if err != mgo.ErrNotFound {
- log.Errorf(err.Error())
- debug.PrintStack()
- }
+ log.Errorf(err.Error())
+ debug.PrintStack()
return node, err
}
return node, nil
diff --git a/backend/model/schedule.go b/backend/model/schedule.go
index bcd051e3..36799ac3 100644
--- a/backend/model/schedule.go
+++ b/backend/model/schedule.go
@@ -45,6 +45,27 @@ func (sch *Schedule) Delete() error {
return c.RemoveId(sch.Id)
}
+func (sch *Schedule) SyncNodeIdAndSpiderId(node Node, spider Spider) {
+ sch.syncNodeId(node)
+ sch.syncSpiderId(spider)
+}
+
+func (sch *Schedule) syncNodeId(node Node) {
+ if node.Id.Hex() == sch.NodeId.Hex() {
+ return
+ }
+ sch.NodeId = node.Id
+ _ = sch.Save()
+}
+
+func (sch *Schedule) syncSpiderId(spider Spider) {
+ if spider.Id.Hex() == sch.SpiderId.Hex() {
+ return
+ }
+ sch.SpiderId = spider.Id
+ _ = sch.Save()
+}
+
func GetScheduleList(filter interface{}) ([]Schedule, error) {
s, c := database.GetCol("schedules")
defer s.Close()
@@ -103,13 +124,11 @@ func UpdateSchedule(id bson.ObjectId, item Schedule) error {
if err := c.FindId(id).One(&result); err != nil {
return err
}
-
node, err := GetNode(item.NodeId)
if err != nil {
- log.Errorf("get node error: %s", err.Error())
- debug.PrintStack()
- return nil
+ return err
}
+
item.NodeKey = node.Key
if err := item.Save(); err != nil {
return err
@@ -123,9 +142,7 @@ func AddSchedule(item Schedule) error {
node, err := GetNode(item.NodeId)
if err != nil {
- log.Errorf("get node error: %s", err.Error())
- debug.PrintStack()
- return nil
+ return err
}
item.Id = bson.NewObjectId()
diff --git a/backend/routes/schedule.go b/backend/routes/schedule.go
index 24df0c0f..73b75323 100644
--- a/backend/routes/schedule.go
+++ b/backend/routes/schedule.go
@@ -45,13 +45,14 @@ func PostSchedule(c *gin.Context) {
HandleError(http.StatusBadRequest, c, err)
return
}
+
+ // 验证cron表达式
+ if err := services.ParserCron(newItem.Cron); err != nil {
+ HandleError(http.StatusOK, c, err)
+ return
+ }
+
newItem.Id = bson.ObjectIdHex(id)
-
- // 如果node_id为空,则置为空ObjectId
- //if newItem.NodeId == "" {
- // newItem.NodeId = bson.ObjectIdHex(constants.ObjectIdNull)
- //}
-
// 更新数据库
if err := model.UpdateSchedule(bson.ObjectIdHex(id), newItem); err != nil {
HandleError(http.StatusInternalServerError, c, err)
@@ -79,10 +80,11 @@ func PutSchedule(c *gin.Context) {
return
}
- // 如果node_id为空,则置为空ObjectId
- //if item.NodeId == "" {
- // item.NodeId = bson.ObjectIdHex(constants.ObjectIdNull)
- //}
+ // 验证cron表达式
+ if err := services.ParserCron(item.Cron); err != nil {
+ HandleError(http.StatusOK, c, err)
+ return
+ }
// 更新数据库
if err := model.AddSchedule(item); err != nil {
diff --git a/backend/services/schedule.go b/backend/services/schedule.go
index f011f02a..d4c1635b 100644
--- a/backend/services/schedule.go
+++ b/backend/services/schedule.go
@@ -5,7 +5,7 @@ import (
"crawlab/lib/cron"
"crawlab/model"
"github.com/apex/log"
- uuid "github.com/satori/go.uuid"
+ "github.com/satori/go.uuid"
"runtime/debug"
)
@@ -31,6 +31,9 @@ func AddTask(s model.Schedule) func() {
return
}
+ // 同步ID到定时任务
+ s.SyncNodeIdAndSpiderId(node, *spider)
+
// 生成任务ID
id := uuid.NewV4()
@@ -119,6 +122,18 @@ func (s *Scheduler) RemoveAll() {
}
}
+// 验证cron表达式是否正确
+func ParserCron(spec string) error {
+ parser := cron.NewParser(
+ cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
+ )
+
+ if _, err := parser.Parse(spec); err != nil {
+ return err
+ }
+ return nil
+}
+
func (s *Scheduler) Update() error {
// 删除所有定时任务
s.RemoveAll()
From 4db8748972c677b2965c81a1b5ce653e4020c6ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E6=99=AF=E9=98=B3?= <1656488874@qq.com>
Date: Wed, 16 Oct 2019 15:27:35 +0800
Subject: [PATCH 07/11] =?UTF-8?q?fix=20=E5=AE=9A=E6=97=B6=E4=BB=BB?=
=?UTF-8?q?=E5=8A=A1=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/views/schedule/ScheduleList.vue | 101 ++++++++++---------
1 file changed, 52 insertions(+), 49 deletions(-)
diff --git a/frontend/src/views/schedule/ScheduleList.vue b/frontend/src/views/schedule/ScheduleList.vue
index 4d283966..b170c9ed 100644
--- a/frontend/src/views/schedule/ScheduleList.vue
+++ b/frontend/src/views/schedule/ScheduleList.vue
@@ -14,7 +14,7 @@
-
+
-
-
-
-
- {{$t('schedules.cron')}}
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
- {{$t('schedules.add_cron')}}
+
+