正在显示
34 个修改的文件
包含
1819 行增加
和
0 行删除
addons/crontab/Crontab.php
0 → 100644
1 | +<?php | ||
2 | + | ||
3 | +namespace addons\crontab; | ||
4 | + | ||
5 | +use app\common\library\Menu; | ||
6 | +use think\Addons; | ||
7 | + | ||
8 | +/** | ||
9 | + * 定时任务 | ||
10 | + */ | ||
11 | +class Crontab extends Addons | ||
12 | +{ | ||
13 | + | ||
14 | + /** | ||
15 | + * 插件安装方法 | ||
16 | + * @return bool | ||
17 | + */ | ||
18 | + public function install() | ||
19 | + { | ||
20 | + $menu = [ | ||
21 | + [ | ||
22 | + 'name' => 'general/crontab', | ||
23 | + 'title' => '定时任务', | ||
24 | + 'icon' => 'fa fa-tasks', | ||
25 | + 'remark' => '类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行,目前支持三种任务:请求URL、执行SQL、执行Shell', | ||
26 | + 'sublist' => [ | ||
27 | + ['name' => 'general/crontab/index', 'title' => '查看'], | ||
28 | + ['name' => 'general/crontab/add', 'title' => '添加'], | ||
29 | + ['name' => 'general/crontab/edit', 'title' => '编辑 '], | ||
30 | + ['name' => 'general/crontab/del', 'title' => '删除'], | ||
31 | + ['name' => 'general/crontab/multi', 'title' => '批量更新'], | ||
32 | + ] | ||
33 | + ] | ||
34 | + ]; | ||
35 | + Menu::create($menu, 'general'); | ||
36 | + return true; | ||
37 | + } | ||
38 | + | ||
39 | + /** | ||
40 | + * 插件卸载方法 | ||
41 | + * @return bool | ||
42 | + */ | ||
43 | + public function uninstall() | ||
44 | + { | ||
45 | + Menu::delete('general/crontab'); | ||
46 | + return true; | ||
47 | + } | ||
48 | + | ||
49 | + /** | ||
50 | + * 插件启用方法 | ||
51 | + */ | ||
52 | + public function enable() | ||
53 | + { | ||
54 | + Menu::enable('general/crontab'); | ||
55 | + } | ||
56 | + | ||
57 | + /** | ||
58 | + * 插件禁用方法 | ||
59 | + */ | ||
60 | + public function disable() | ||
61 | + { | ||
62 | + Menu::disable('general/crontab'); | ||
63 | + } | ||
64 | + | ||
65 | +} |
1 | +<?php | ||
2 | + | ||
3 | +namespace app\admin\controller\general; | ||
4 | + | ||
5 | +use app\common\controller\Backend; | ||
6 | +use Cron\CronExpression; | ||
7 | + | ||
8 | +/** | ||
9 | + * 定时任务 | ||
10 | + * | ||
11 | + * @icon fa fa-tasks | ||
12 | + * @remark 类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行 | ||
13 | + */ | ||
14 | +class Crontab extends Backend | ||
15 | +{ | ||
16 | + | ||
17 | + protected $model = null; | ||
18 | + protected $noNeedRight = ['check_schedule', 'get_schedule_future']; | ||
19 | + | ||
20 | + public function _initialize() | ||
21 | + { | ||
22 | + parent::_initialize(); | ||
23 | + $this->model = model('Crontab'); | ||
24 | + $this->view->assign('typeList', \app\admin\model\Crontab::getTypeList()); | ||
25 | + $this->assignconfig('typeList', \app\admin\model\Crontab::getTypeList()); | ||
26 | + } | ||
27 | + | ||
28 | + /** | ||
29 | + * 查看 | ||
30 | + */ | ||
31 | + public function index() | ||
32 | + { | ||
33 | + if ($this->request->isAjax()) { | ||
34 | + list($where, $sort, $order, $offset, $limit) = $this->buildparams(); | ||
35 | + $total = $this->model | ||
36 | + ->where($where) | ||
37 | + ->order($sort, $order) | ||
38 | + ->count(); | ||
39 | + | ||
40 | + $list = $this->model | ||
41 | + ->where($where) | ||
42 | + ->order($sort, $order) | ||
43 | + ->limit($offset, $limit) | ||
44 | + ->select(); | ||
45 | + $time = time(); | ||
46 | + foreach ($list as $k => &$v) { | ||
47 | + $cron = CronExpression::factory($v['schedule']); | ||
48 | + $v['nexttime'] = $time > $v['endtime'] ? __('None') : $cron->getNextRunDate()->getTimestamp(); | ||
49 | + } | ||
50 | + $result = array("total" => $total, "rows" => $list); | ||
51 | + | ||
52 | + return json($result); | ||
53 | + } | ||
54 | + return $this->view->fetch(); | ||
55 | + } | ||
56 | + | ||
57 | + /** | ||
58 | + * 判断Crontab格式是否正确 | ||
59 | + * @internal | ||
60 | + */ | ||
61 | + public function check_schedule() | ||
62 | + { | ||
63 | + $row = $this->request->post("row/a"); | ||
64 | + $schedule = isset($row['schedule']) ? $row['schedule'] : ''; | ||
65 | + if (CronExpression::isValidExpression($schedule)) { | ||
66 | + $this->success(); | ||
67 | + } else { | ||
68 | + $this->error(__('Crontab format invalid')); | ||
69 | + } | ||
70 | + } | ||
71 | + | ||
72 | + /** | ||
73 | + * 根据Crontab表达式读取未来七次的时间 | ||
74 | + * @internal | ||
75 | + */ | ||
76 | + public function get_schedule_future() | ||
77 | + { | ||
78 | + $time = []; | ||
79 | + $schedule = $this->request->post('schedule'); | ||
80 | + $days = (int)$this->request->post('days'); | ||
81 | + try { | ||
82 | + $cron = CronExpression::factory($schedule); | ||
83 | + for ($i = 0; $i < $days; $i++) { | ||
84 | + $time[] = $cron->getNextRunDate(null, $i)->format('Y-m-d H:i:s'); | ||
85 | + } | ||
86 | + } catch (\Exception $e) { | ||
87 | + | ||
88 | + } | ||
89 | + | ||
90 | + $this->success("", null, ['futuretime' => $time]); | ||
91 | + } | ||
92 | + | ||
93 | +} |
1 | +<?php | ||
2 | + | ||
3 | +namespace app\admin\controller\general; | ||
4 | + | ||
5 | +use app\common\controller\Backend; | ||
6 | + | ||
7 | +/** | ||
8 | + * 定时任务 | ||
9 | + * | ||
10 | + * @icon fa fa-tasks | ||
11 | + * @remark 类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行 | ||
12 | + */ | ||
13 | +class CrontabLog extends Backend | ||
14 | +{ | ||
15 | + | ||
16 | + protected $model = null; | ||
17 | + | ||
18 | + public function _initialize() | ||
19 | + { | ||
20 | + parent::_initialize(); | ||
21 | + $this->model = model('CrontabLog'); | ||
22 | + $this->view->assign('statusList', $this->model->getStatusList()); | ||
23 | + $this->assignconfig('statusList', $this->model->getStatusList()); | ||
24 | + } | ||
25 | + | ||
26 | + /** | ||
27 | + * 查看 | ||
28 | + */ | ||
29 | + public function index() | ||
30 | + { | ||
31 | + if ($this->request->isAjax()) { | ||
32 | + list($where, $sort, $order, $offset, $limit) = $this->buildparams(); | ||
33 | + $total = $this->model | ||
34 | + ->where($where) | ||
35 | + ->order($sort, $order) | ||
36 | + ->count(); | ||
37 | + | ||
38 | + $list = $this->model | ||
39 | + ->where($where) | ||
40 | + ->order($sort, $order) | ||
41 | + ->limit($offset, $limit) | ||
42 | + ->select(); | ||
43 | + $list = collection($list)->toArray(); | ||
44 | + $result = array("total" => $total, "rows" => $list); | ||
45 | + | ||
46 | + return json($result); | ||
47 | + } | ||
48 | + return $this->view->fetch(); | ||
49 | + } | ||
50 | + | ||
51 | + public function detail($ids = null) | ||
52 | + { | ||
53 | + $row = $this->model->get($ids); | ||
54 | + if (!$row) { | ||
55 | + $this->error(__('No Results were found')); | ||
56 | + } | ||
57 | + $this->view->assign("row", $row); | ||
58 | + return $this->view->fetch(); | ||
59 | + } | ||
60 | + | ||
61 | +} |
1 | +<?php | ||
2 | + | ||
3 | +return [ | ||
4 | + 'Title' => '任务标题', | ||
5 | + 'Maximums' => '最多执行', | ||
6 | + 'Sleep' => '延迟秒数', | ||
7 | + 'Schedule' => '执行周期', | ||
8 | + 'Executes' => '执行次数', | ||
9 | + 'Completed' => '已完成', | ||
10 | + 'Expired' => '已过期', | ||
11 | + 'Hidden' => '已禁用', | ||
12 | + 'Logs' => '日志信息', | ||
13 | + 'Crontab rules' => 'Crontab规则', | ||
14 | + 'No limit' => '无限制', | ||
15 | + 'Execute time' => '最后执行时间', | ||
16 | + 'Request Url' => '请求URL', | ||
17 | + 'Execute Sql Script' => '执行SQL', | ||
18 | + 'Execute Shell' => '执行Shell', | ||
19 | + 'Crontab format invalid' => 'Crontab格式错误', | ||
20 | + 'Next execute time' => '下次预计时间', | ||
21 | + 'The next %s times the execution time' => '接下来 %s 次的执行时间', | ||
22 | +]; |
1 | +<?php | ||
2 | + | ||
3 | +namespace app\admin\model; | ||
4 | + | ||
5 | +use think\Model; | ||
6 | + | ||
7 | +class Crontab extends Model | ||
8 | +{ | ||
9 | + | ||
10 | + // 开启自动写入时间戳字段 | ||
11 | + protected $autoWriteTimestamp = 'int'; | ||
12 | + // 定义时间戳字段名 | ||
13 | + protected $createTime = 'createtime'; | ||
14 | + protected $updateTime = 'updatetime'; | ||
15 | + // 定义字段类型 | ||
16 | + protected $type = [ | ||
17 | + ]; | ||
18 | + // 追加属性 | ||
19 | + protected $append = [ | ||
20 | + 'type_text' | ||
21 | + ]; | ||
22 | + | ||
23 | + public static function getTypeList() | ||
24 | + { | ||
25 | + return [ | ||
26 | + 'url' => __('Request Url'), | ||
27 | + 'sql' => __('Execute Sql Script'), | ||
28 | + 'shell' => __('Execute Shell'), | ||
29 | + ]; | ||
30 | + } | ||
31 | + | ||
32 | + public function getTypeTextAttr($value, $data) | ||
33 | + { | ||
34 | + $typelist = self::getTypeList(); | ||
35 | + $value = $value ? $value : $data['type']; | ||
36 | + return $value && isset($typelist[$value]) ? $typelist[$value] : $value; | ||
37 | + } | ||
38 | + | ||
39 | + protected function setBegintimeAttr($value) | ||
40 | + { | ||
41 | + return $value && !is_numeric($value) ? strtotime($value) : $value; | ||
42 | + } | ||
43 | + | ||
44 | + protected function setEndtimeAttr($value) | ||
45 | + { | ||
46 | + return $value && !is_numeric($value) ? strtotime($value) : $value; | ||
47 | + } | ||
48 | + | ||
49 | + protected function setExecutetimeAttr($value) | ||
50 | + { | ||
51 | + return $value && !is_numeric($value) ? strtotime($value) : $value; | ||
52 | + } | ||
53 | + | ||
54 | +} |
1 | +<?php | ||
2 | + | ||
3 | +namespace app\admin\model; | ||
4 | + | ||
5 | +use think\Model; | ||
6 | + | ||
7 | +class CrontabLog extends Model | ||
8 | +{ | ||
9 | + | ||
10 | + // 开启自动写入时间戳字段 | ||
11 | + protected $autoWriteTimestamp = 'int'; | ||
12 | + // 定义时间戳字段名 | ||
13 | + protected $createTime = false; | ||
14 | + protected $updateTime = false; | ||
15 | + // 定义字段类型 | ||
16 | + protected $type = [ | ||
17 | + ]; | ||
18 | + // 追加属性 | ||
19 | + protected $append = [ | ||
20 | + ]; | ||
21 | + | ||
22 | + public function getStatusList() | ||
23 | + { | ||
24 | + return ['normal' => __('Normal'), 'hidden' => __('Hidden')]; | ||
25 | + } | ||
26 | + | ||
27 | + public function getStatusTextAttr($value, $data) | ||
28 | + { | ||
29 | + $value = $value ? $value : (isset($data['status']) ? $data['status'] : ''); | ||
30 | + $list = $this->getStatusList(); | ||
31 | + return isset($list[$value]) ? $list[$value] : ''; | ||
32 | + } | ||
33 | + | ||
34 | +} |
1 | +<style type="text/css"> | ||
2 | + #schedulepicker { | ||
3 | + padding-top:7px; | ||
4 | + } | ||
5 | +</style> | ||
6 | +<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action=""> | ||
7 | + <div class="form-group"> | ||
8 | + <label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label> | ||
9 | + <div class="col-xs-12 col-sm-8"> | ||
10 | + <input type="text" class="form-control" id="title" name="row[title]" value="" data-rule="required" /> | ||
11 | + </div> | ||
12 | + </div> | ||
13 | + <div class="form-group"> | ||
14 | + <label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label> | ||
15 | + <div class="col-xs-12 col-sm-8"> | ||
16 | + {:build_select('row[type]', $typeList, null, ['class'=>'form-control', 'data-rule'=>'required'])} | ||
17 | + </div> | ||
18 | + </div> | ||
19 | + <div class="form-group"> | ||
20 | + <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label> | ||
21 | + <div class="col-xs-12 col-sm-8"> | ||
22 | + <textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required"></textarea> | ||
23 | + </div> | ||
24 | + </div> | ||
25 | + <div class="form-group"> | ||
26 | + <label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label> | ||
27 | + <div class="col-xs-12 col-sm-8"> | ||
28 | + <div class="input-group margin-bottom-sm"> | ||
29 | + <input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="* * * * *" data-rule="required; remote(general/crontab/check_schedule)"/> | ||
30 | + <span class="input-group-btn"> | ||
31 | + <a href="https://www.fastadmin.net/store/crontab.html" target="_blank" class="btn btn-default"><i class="fa fa-info-circle"></i> {:__('Crontab rules')}</a> | ||
32 | + </span> | ||
33 | + <span class="msg-box n-right"></span> | ||
34 | + </div> | ||
35 | + <div id="schedulepicker"> | ||
36 | + <pre><code>* * * * * | ||
37 | +- - - - - | ||
38 | +| | | | +--- day of week (0 - 7) (Sunday=0 or 7) | ||
39 | +| | | +-------- month (1 - 12) | ||
40 | +| | +------------- day of month (1 - 31) | ||
41 | +| +------------------ hour (0 - 23) | ||
42 | ++----------------------- min (0 - 59)</code></pre> | ||
43 | + <h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5> | ||
44 | + <ol id="scheduleresult" class="list-group"> | ||
45 | + </ol> | ||
46 | + </div> | ||
47 | + </div> | ||
48 | + </div> | ||
49 | + <div class="form-group"> | ||
50 | + <label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label> | ||
51 | + <div class="col-xs-12 col-sm-4"> | ||
52 | + <input type="number" class="form-control" id="maximums" name="row[maximums]" value="0" data-rule="required" size="6" /> | ||
53 | + </div> | ||
54 | + </div> | ||
55 | + <div class="form-group"> | ||
56 | + <label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label> | ||
57 | + <div class="col-xs-12 col-sm-4"> | ||
58 | + <input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="" data-rule="{:__('Begin time')}:required" size="6" /> | ||
59 | + </div> | ||
60 | + </div> | ||
61 | + <div class="form-group"> | ||
62 | + <label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label> | ||
63 | + <div class="col-xs-12 col-sm-4"> | ||
64 | + <input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" /> | ||
65 | + </div> | ||
66 | + </div> | ||
67 | + <div class="form-group"> | ||
68 | + <label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label> | ||
69 | + <div class="col-xs-12 col-sm-4"> | ||
70 | + <input type="text" class="form-control" id="weigh" name="row[weigh]" value="0" data-rule="required" size="6" /> | ||
71 | + </div> | ||
72 | + </div> | ||
73 | + <div class="form-group"> | ||
74 | + <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label> | ||
75 | + <div class="col-xs-12 col-sm-8"> | ||
76 | + {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])} | ||
77 | + </div> | ||
78 | + </div> | ||
79 | + <div class="form-group hide layer-footer"> | ||
80 | + <label class="control-label col-xs-12 col-sm-2"></label> | ||
81 | + <div class="col-xs-12 col-sm-8"> | ||
82 | + <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button> | ||
83 | + <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button> | ||
84 | + </div> | ||
85 | + </div> | ||
86 | + | ||
87 | +</form> |
1 | +<style type="text/css"> | ||
2 | + #schedulepicker { | ||
3 | + padding-top: 7px; | ||
4 | + } | ||
5 | +</style> | ||
6 | +<form id="edit-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action=""> | ||
7 | + <div class="form-group"> | ||
8 | + <label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label> | ||
9 | + <div class="col-xs-12 col-sm-8"> | ||
10 | + <input type="text" class="form-control" id="title" name="row[title]" value="{$row.title}" data-rule="required"/> | ||
11 | + </div> | ||
12 | + </div> | ||
13 | + <div class="form-group"> | ||
14 | + <label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label> | ||
15 | + <div class="col-xs-12 col-sm-8"> | ||
16 | + {:build_select('row[type]', $typeList, $row['type'], ['class'=>'form-control', 'data-rule'=>'required'])} | ||
17 | + </div> | ||
18 | + </div> | ||
19 | + <div class="form-group"> | ||
20 | + <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label> | ||
21 | + <div class="col-xs-12 col-sm-8"> | ||
22 | + <textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required">{$row.content}</textarea> | ||
23 | + </div> | ||
24 | + </div> | ||
25 | + <div class="form-group"> | ||
26 | + <label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label> | ||
27 | + <div class="col-xs-12 col-sm-8"> | ||
28 | + <div class="input-group margin-bottom-sm"> | ||
29 | + <input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="{$row.schedule}" data-rule="required; remote(general/crontab/check_schedule)"/> | ||
30 | + <span class="input-group-btn"> | ||
31 | + <a href="https://www.fastadmin.net/store/crontab.html" target="_blank" class="btn btn-default"><i class="fa fa-info-circle"></i> {:__('Crontab rules')}</a> | ||
32 | + </span> | ||
33 | + <span class="msg-box n-right"></span> | ||
34 | + </div> | ||
35 | + | ||
36 | + <div id="schedulepicker"> | ||
37 | + <pre><code>* * * * * | ||
38 | +- - - - - | ||
39 | +| | | | +--- day of week (0 - 7) (Sunday=0 or 7) | ||
40 | +| | | +-------- month (1 - 12) | ||
41 | +| | +------------- day of month (1 - 31) | ||
42 | +| +------------------ hour (0 - 23) | ||
43 | ++----------------------- min (0 - 59)</code></pre> | ||
44 | + <h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5> | ||
45 | + <ol id="scheduleresult" class="list-group"> | ||
46 | + </ol> | ||
47 | + </div> | ||
48 | + </div> | ||
49 | + </div> | ||
50 | + <div class="form-group"> | ||
51 | + <label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label> | ||
52 | + <div class="col-xs-12 col-sm-4"> | ||
53 | + <input type="number" class="form-control" id="maximums" name="row[maximums]" value="{$row.maximums}" data-rule="required" size="6"/> | ||
54 | + </div> | ||
55 | + </div> | ||
56 | + <div class="form-group"> | ||
57 | + <label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label> | ||
58 | + <div class="col-xs-12 col-sm-4"> | ||
59 | + <input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="{$row.begintime|datetime}" data-rule="{:__('Begin time')}:required" size="6"/> | ||
60 | + </div> | ||
61 | + </div> | ||
62 | + <div class="form-group"> | ||
63 | + <label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label> | ||
64 | + <div class="col-xs-12 col-sm-4"> | ||
65 | + <input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="{$row.endtime|datetime}" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6"/> | ||
66 | + </div> | ||
67 | + </div> | ||
68 | + <div class="form-group"> | ||
69 | + <label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label> | ||
70 | + <div class="col-xs-12 col-sm-4"> | ||
71 | + <input type="text" class="form-control" id="weigh" name="row[weigh]" value="{$row.weigh}" data-rule="required" size="6"/> | ||
72 | + </div> | ||
73 | + </div> | ||
74 | + <div class="form-group"> | ||
75 | + <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label> | ||
76 | + <div class="col-xs-12 col-sm-8"> | ||
77 | + {:build_radios('row[status]', ['normal'=>__('Normal'), 'completed'=>__('Completed'), 'expired'=>__('Expired'), 'hidden'=>__('Hidden')], $row['status'])} | ||
78 | + </div> | ||
79 | + </div> | ||
80 | + <div class="form-group hide layer-footer"> | ||
81 | + <label class="control-label col-xs-12 col-sm-2"></label> | ||
82 | + <div class="col-xs-12 col-sm-8"> | ||
83 | + <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button> | ||
84 | + <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button> | ||
85 | + </div> | ||
86 | + </div> | ||
87 | + | ||
88 | +</form> |
1 | +<div class="panel panel-default panel-intro"> | ||
2 | + | ||
3 | + <div class="panel-heading"> | ||
4 | + {:build_heading(null,FALSE)} | ||
5 | + <ul class="nav nav-tabs" data-field="type"> | ||
6 | + <li class="active"><a href="#t-all" data-value="" data-toggle="tab">{:__('All')}</a></li> | ||
7 | + {foreach name="typeList" item="vo"} | ||
8 | + <li><a href="#t-{$key}" data-value="{$key}" data-toggle="tab">{$vo}</a></li> | ||
9 | + {/foreach} | ||
10 | + </ul> | ||
11 | + </div> | ||
12 | + | ||
13 | + <div class="panel-body"> | ||
14 | + <div id="myTabContent" class="tab-content"> | ||
15 | + <div class="tab-pane fade active in" id="one"> | ||
16 | + <div class="widget-body no-padding"> | ||
17 | + <div id="toolbar" class="toolbar"> | ||
18 | + {:build_toolbar('refresh,add,edit,del')} | ||
19 | + </div> | ||
20 | + <table id="table" class="table table-striped table-bordered table-hover" | ||
21 | + data-operate-edit="{:$auth->check('general/crontab/edit')}" | ||
22 | + data-operate-del="{:$auth->check('general/crontab/del')}" | ||
23 | + width="100%"> | ||
24 | + </table> | ||
25 | + </div> | ||
26 | + </div> | ||
27 | + | ||
28 | + </div> | ||
29 | + </div> | ||
30 | +</div> |
1 | +<style type="text/css"> | ||
2 | + #schedulepicker { | ||
3 | + padding-top:7px; | ||
4 | + } | ||
5 | +</style> | ||
6 | +<form id="edit-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action=""> | ||
7 | + <div class="form-group"> | ||
8 | + <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label> | ||
9 | + <div class="col-xs-12 col-sm-10"> | ||
10 | + <textarea name="row[content]" id="conent" cols="30" style="width:100%;" rows="20" class="form-control" data-rule="required" readonly>{$row.content|htmlentities}</textarea> | ||
11 | + </div> | ||
12 | + </div> | ||
13 | + <div class="form-group"> | ||
14 | + <label for="executetime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label> | ||
15 | + <div class="col-xs-12 col-sm-4"> | ||
16 | + <input type="text" class="form-control datetimepicker" id="executetime" name="row[executetime]" value="{$row.executetime|datetime}" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" disabled /> | ||
17 | + </div> | ||
18 | + </div> | ||
19 | + <div class="form-group"> | ||
20 | + <label for="completetime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label> | ||
21 | + <div class="col-xs-12 col-sm-4"> | ||
22 | + <input type="text" class="form-control datetimepicker" id="completetime" name="row[completetime]" value="{$row.completetime|datetime}" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" disabled /> | ||
23 | + </div> | ||
24 | + </div> | ||
25 | + <div class="form-group"> | ||
26 | + <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label> | ||
27 | + <div class="col-xs-12 col-sm-8"> | ||
28 | + <div style="padding-top:8px;"> | ||
29 | + {if $row['status']=='success'}<span class="label label-success">{:__('Success')}</span>{else/}<span class="label label-danger">{:__('Failure')}</span>{/if} | ||
30 | + </div> | ||
31 | + </div> | ||
32 | + </div> | ||
33 | + <div class="form-group hide layer-footer"> | ||
34 | + <label class="control-label col-xs-12 col-sm-2"></label> | ||
35 | + <div class="col-xs-12 col-sm-8"> | ||
36 | + <button type="button" class="btn btn-success btn-embossed" onclick="parent.Layer.close(parent.Layer.getFrameIndex(window.name))">{:__('Close')}</button> | ||
37 | + </div> | ||
38 | + </div> | ||
39 | + | ||
40 | +</form> |
1 | +<div class="panel panel-default panel-intro"> | ||
2 | + | ||
3 | + <div class="panel-heading"> | ||
4 | + {:build_heading(null,FALSE)} | ||
5 | + <ul class="nav nav-tabs" data-field="status"> | ||
6 | + <li class="active"><a href="#t-all" data-value="" data-toggle="tab">{:__('All')}</a></li> | ||
7 | + {foreach name="statusList" item="vo"} | ||
8 | + <li><a href="#t-{$key}" data-value="{$key}" data-toggle="tab">{$vo}</a></li> | ||
9 | + {/foreach} | ||
10 | + </ul> | ||
11 | + </div> | ||
12 | + | ||
13 | + <div class="panel-body"> | ||
14 | + <div id="myTabContent" class="tab-content"> | ||
15 | + <div class="tab-pane fade active in" id="one"> | ||
16 | + <div class="widget-body no-padding"> | ||
17 | + <div id="toolbar" class="toolbar"> | ||
18 | + {:build_toolbar('refresh,del')} | ||
19 | + </div> | ||
20 | + <table id="table" class="table table-striped table-bordered table-hover" | ||
21 | + data-operate-detail="{:$auth->check('general/crontab/detail')}" | ||
22 | + data-operate-del="{:$auth->check('general/crontab/del')}" | ||
23 | + width="100%"> | ||
24 | + </table> | ||
25 | + </div> | ||
26 | + </div> | ||
27 | + | ||
28 | + </div> | ||
29 | + </div> | ||
30 | +</div> |
addons/crontab/config.php
0 → 100644
addons/crontab/controller/Autotask.php
0 → 100644
1 | +<?php | ||
2 | + | ||
3 | +namespace addons\crontab\controller; | ||
4 | + | ||
5 | +use addons\crontab\model\Crontab; | ||
6 | +use Cron\CronExpression; | ||
7 | +use fast\Http; | ||
8 | +use think\Controller; | ||
9 | +use think\Db; | ||
10 | +use think\Exception; | ||
11 | +use think\Log; | ||
12 | + | ||
13 | +/** | ||
14 | + * 定时任务接口 | ||
15 | + * | ||
16 | + * 以Crontab方式每分钟定时执行,且只可以Cli方式运行 | ||
17 | + * @internal | ||
18 | + */ | ||
19 | +class Autotask extends Controller | ||
20 | +{ | ||
21 | + | ||
22 | + /** | ||
23 | + * 初始化方法,最前且始终执行 | ||
24 | + */ | ||
25 | + public function _initialize() | ||
26 | + { | ||
27 | + // 只可以以cli方式执行 | ||
28 | + if (!$this->request->isCli()) { | ||
29 | + $this->error('Autotask script only work at client!'); | ||
30 | + } | ||
31 | + | ||
32 | + parent::_initialize(); | ||
33 | + | ||
34 | + // 清除错误 | ||
35 | + error_reporting(0); | ||
36 | + | ||
37 | + // 设置永不超时 | ||
38 | + set_time_limit(0); | ||
39 | + } | ||
40 | + | ||
41 | + /** | ||
42 | + * 执行定时任务 | ||
43 | + */ | ||
44 | + public function index() | ||
45 | + { | ||
46 | + $time = time(); | ||
47 | + $logDir = LOG_PATH . 'crontab/'; | ||
48 | + if (!is_dir($logDir)) { | ||
49 | + mkdir($logDir, 0755); | ||
50 | + } | ||
51 | + //筛选未过期且未完成的任务 | ||
52 | + $crontabList = Crontab::where('status', '=', 'normal')->order('weigh desc,id desc')->select(); | ||
53 | + $execTime = time(); | ||
54 | + foreach ($crontabList as $crontab) { | ||
55 | + $update = []; | ||
56 | + $execute = false; | ||
57 | + if ($time < $crontab['begintime']) { | ||
58 | + //任务未开始 | ||
59 | + continue; | ||
60 | + } | ||
61 | + if ($crontab['maximums'] && $crontab['executes'] > $crontab['maximums']) { | ||
62 | + //任务已超过最大执行次数 | ||
63 | + $update['status'] = 'completed'; | ||
64 | + } else { | ||
65 | + if ($crontab['endtime'] > 0 && $time > $crontab['endtime']) { | ||
66 | + //任务已过期 | ||
67 | + $update['status'] = 'expired'; | ||
68 | + } else { | ||
69 | + //重复执行 | ||
70 | + //如果未到执行时间则继续循环 | ||
71 | + $cron = CronExpression::factory($crontab['schedule']); | ||
72 | + if (!$cron->isDue(date("YmdHi", $execTime)) || date("YmdHi", $execTime) === date("YmdHi", $crontab['executetime'])) { | ||
73 | + continue; | ||
74 | + } | ||
75 | + $execute = true; | ||
76 | + } | ||
77 | + } | ||
78 | + | ||
79 | + // 如果允许执行 | ||
80 | + if ($execute) { | ||
81 | + $update['executetime'] = $time; | ||
82 | + $update['executes'] = $crontab['executes'] + 1; | ||
83 | + $update['status'] = ($crontab['maximums'] > 0 && $update['executes'] >= $crontab['maximums']) ? 'completed' : 'normal'; | ||
84 | + } | ||
85 | + | ||
86 | + // 如果需要更新状态 | ||
87 | + if (!$update) { | ||
88 | + continue; | ||
89 | + } | ||
90 | + // 更新状态 | ||
91 | + $crontab->save($update); | ||
92 | + | ||
93 | + // 将执行放在后面是为了避免超时导致多次执行 | ||
94 | + if (!$execute) { | ||
95 | + continue; | ||
96 | + } | ||
97 | + $result = false; | ||
98 | + $message = ''; | ||
99 | + | ||
100 | + try { | ||
101 | + if ($crontab['type'] == 'url') { | ||
102 | + if (substr($crontab['content'], 0, 1) == "/") { | ||
103 | + // 本地项目URL | ||
104 | + $message = shell_exec('php ' . ROOT_PATH . 'public/index.php ' . $crontab['content']); | ||
105 | + $result = $message ? true : false; | ||
106 | + } else { | ||
107 | + $arr = explode(" ", $crontab['content']); | ||
108 | + $url = $arr[0]; | ||
109 | + $params = isset($arr[1]) ? $arr[1] : ''; | ||
110 | + $method = isset($arr[2]) ? $arr[2] : 'POST'; | ||
111 | + try { | ||
112 | + // 远程异步调用URL | ||
113 | + $ret = Http::sendRequest($url, $params, $method); | ||
114 | + $result = $ret['ret']; | ||
115 | + $message = $ret['msg']; | ||
116 | + } catch (\Exception $e) { | ||
117 | + $message = $e->getMessage(); | ||
118 | + } | ||
119 | + } | ||
120 | + | ||
121 | + } elseif ($crontab['type'] == 'sql') { | ||
122 | + $ret = $this->sql($crontab['content']); | ||
123 | + $result = $ret['ret']; | ||
124 | + $message = $ret['msg']; | ||
125 | + } elseif ($crontab['type'] == 'shell') { | ||
126 | + // 执行Shell | ||
127 | + $message = shell_exec($crontab['content']); | ||
128 | + $result = $message ? true : false; | ||
129 | + } | ||
130 | + } catch (\Exception $e) { | ||
131 | + $message = $e->getMessage(); | ||
132 | + } | ||
133 | + $log = [ | ||
134 | + 'crontab_id' => $crontab['id'], | ||
135 | + 'executetime' => $time, | ||
136 | + 'completetime' => time(), | ||
137 | + 'content' => $message, | ||
138 | + 'status' => $result ? 'success' : 'failure', | ||
139 | + ]; | ||
140 | + Db::name("crontab_log")->insert($log); | ||
141 | + } | ||
142 | + return "Execute completed!\n"; | ||
143 | + } | ||
144 | + | ||
145 | + /** | ||
146 | + * 执行SQL语句 | ||
147 | + */ | ||
148 | + protected function sql($sql) | ||
149 | + { | ||
150 | + //这里需要强制重连数据库,使用已有的连接会报2014错误 | ||
151 | + $connect = Db::connect([], true); | ||
152 | + $connect->execute("select 1"); | ||
153 | + | ||
154 | + // 执行SQL | ||
155 | + $sqlquery = str_replace('__PREFIX__', config('database.prefix'), $sql); | ||
156 | + $sqls = preg_split("/;[ \t]{0,}\n/i", $sqlquery); | ||
157 | + | ||
158 | + $result = false; | ||
159 | + $message = ''; | ||
160 | + $connect->startTrans(); | ||
161 | + try { | ||
162 | + foreach ($sqls as $key => $val) { | ||
163 | + if (trim($val) == '' || substr($val, 0, 2) == '--' || substr($val, 0, 2) == '/*') { | ||
164 | + continue; | ||
165 | + } | ||
166 | + $message .= "\nSQL:{$val}\n"; | ||
167 | + $val = rtrim($val, ';'); | ||
168 | + if (preg_match("/^(select|explain)(.*)/i ", $val)) { | ||
169 | + $count = $connect->execute($val); | ||
170 | + if ($count > 0) { | ||
171 | + $resultlist = Db::query($val); | ||
172 | + } else { | ||
173 | + $resultlist = []; | ||
174 | + } | ||
175 | + | ||
176 | + $message .= "Total:{$count}\n"; | ||
177 | + $j = 1; | ||
178 | + foreach ($resultlist as $m => $n) { | ||
179 | + $message .= "\n"; | ||
180 | + $message .= "Row:{$j}\n"; | ||
181 | + foreach ($n as $k => $v) { | ||
182 | + $message .= "{$k}:{$v}\n"; | ||
183 | + } | ||
184 | + $j++; | ||
185 | + } | ||
186 | + } else { | ||
187 | + $count = $connect->getPdo()->exec($val); | ||
188 | + $message = "Affected rows:{$count}"; | ||
189 | + } | ||
190 | + } | ||
191 | + $connect->commit(); | ||
192 | + $result = true; | ||
193 | + } catch (\PDOException $e) { | ||
194 | + $message = $e->getMessage(); | ||
195 | + $connect->rollback(); | ||
196 | + $result = false; | ||
197 | + } | ||
198 | + return ['ret' => $result, 'msg' => $message]; | ||
199 | + | ||
200 | + } | ||
201 | +} |
addons/crontab/controller/Index.php
0 → 100644
addons/crontab/info.ini
0 → 100644
addons/crontab/install.sql
0 → 100644
1 | +CREATE TABLE IF NOT EXISTS `__PREFIX__crontab` ( | ||
2 | + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID', | ||
3 | + `type` varchar(10) NOT NULL DEFAULT '' COMMENT '事件类型', | ||
4 | + `title` varchar(100) NOT NULL DEFAULT '' COMMENT '事件标题', | ||
5 | + `content` text NOT NULL COMMENT '事件内容', | ||
6 | + `schedule` varchar(100) NOT NULL DEFAULT '' COMMENT 'Crontab格式', | ||
7 | + `sleep` tinyint(1) UNSIGNED NOT NULL DEFAULT '0' COMMENT '延迟秒数执行', | ||
8 | + `maximums` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '最大执行次数 0为不限', | ||
9 | + `executes` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '已经执行的次数', | ||
10 | + `createtime` int(10) DEFAULT NULL COMMENT '创建时间', | ||
11 | + `updatetime` int(10) DEFAULT NULL COMMENT '更新时间', | ||
12 | + `begintime` int(10) DEFAULT NULL COMMENT '开始时间', | ||
13 | + `endtime` int(10) DEFAULT NULL COMMENT '结束时间', | ||
14 | + `executetime` int(10) DEFAULT NULL COMMENT '最后执行时间', | ||
15 | + `weigh` int(10) NOT NULL DEFAULT '0' COMMENT '权重', | ||
16 | + `status` enum('completed','expired','hidden','normal') NOT NULL DEFAULT 'normal' COMMENT '状态', | ||
17 | + PRIMARY KEY (`id`) | ||
18 | +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='定时任务表'; | ||
19 | + | ||
20 | +BEGIN; | ||
21 | +INSERT INTO `__PREFIX__crontab` (`id`, `type`, `title`, `content`, `schedule`, `sleep`, `maximums`, `executes`, `createtime`, `updatetime`, `begintime`, `endtime`, `executetime`, `weigh`, `status`) VALUES | ||
22 | +(1, 'url', '请求百度', 'https://www.baidu.com', '* * * * *', 0, 0, 0, 1497070825, 1501253101, 1483200000, 1830268800, 1501253101, 1, 'normal'), | ||
23 | +(2, 'sql', '查询一条SQL', 'SELECT 1;', '* * * * *', 0, 0, 0, 1497071095, 1501253101, 1483200000, 1830268800, 1501253101, 2, 'normal'); | ||
24 | +COMMIT; | ||
25 | + | ||
26 | +CREATE TABLE IF NOT EXISTS `__PREFIX__crontab_log` ( | ||
27 | + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, | ||
28 | + `crontab_id` int(10) DEFAULT NULL COMMENT '任务ID', | ||
29 | + `executetime` int(10) DEFAULT NULL COMMENT '执行时间', | ||
30 | + `completetime` int(10) DEFAULT NULL COMMENT '结束时间', | ||
31 | + `content` text COMMENT '执行结果', | ||
32 | + `status` enum('success','failure') DEFAULT 'failure' COMMENT '状态', | ||
33 | + PRIMARY KEY (`id`), | ||
34 | + KEY `crontab_id` (`crontab_id`) | ||
35 | +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='定时任务日志表'; |
addons/crontab/model/Crontab.php
0 → 100644
1 | +<?php | ||
2 | + | ||
3 | +namespace addons\crontab\model; | ||
4 | + | ||
5 | +use think\Model; | ||
6 | + | ||
7 | +class Crontab extends Model | ||
8 | +{ | ||
9 | + | ||
10 | + // 开启自动写入时间戳字段 | ||
11 | + protected $autoWriteTimestamp = 'int'; | ||
12 | + // 定义时间戳字段名 | ||
13 | + protected $createTime = 'createtime'; | ||
14 | + protected $updateTime = 'updatetime'; | ||
15 | + // 定义字段类型 | ||
16 | + protected $type = [ | ||
17 | + ]; | ||
18 | + // 追加属性 | ||
19 | + protected $append = [ | ||
20 | + 'type_text' | ||
21 | + ]; | ||
22 | + | ||
23 | + public static function getTypeList() | ||
24 | + { | ||
25 | + return [ | ||
26 | + 'url' => __('Request Url'), | ||
27 | + 'sql' => __('Execute Sql Script'), | ||
28 | + 'shell' => __('Execute Shell'), | ||
29 | + ]; | ||
30 | + } | ||
31 | + | ||
32 | + public function getTypeTextAttr($value, $data) | ||
33 | + { | ||
34 | + $typelist = self::getTypeList(); | ||
35 | + $value = $value ? $value : $data['type']; | ||
36 | + return $value && isset($typelist[$value]) ? $typelist[$value] : $value; | ||
37 | + } | ||
38 | + | ||
39 | + protected function setBegintimeAttr($value) | ||
40 | + { | ||
41 | + return $value && !is_numeric($value) ? strtotime($value) : $value; | ||
42 | + } | ||
43 | + | ||
44 | + protected function setEndtimeAttr($value) | ||
45 | + { | ||
46 | + return $value && !is_numeric($value) ? strtotime($value) : $value; | ||
47 | + } | ||
48 | + | ||
49 | + protected function setExecutetimeAttr($value) | ||
50 | + { | ||
51 | + return $value && !is_numeric($value) ? strtotime($value) : $value; | ||
52 | + } | ||
53 | + | ||
54 | +} |
1 | +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { | ||
2 | + | ||
3 | + var Controller = { | ||
4 | + index: function () { | ||
5 | + // 初始化表格参数配置 | ||
6 | + Table.api.init({ | ||
7 | + extend: { | ||
8 | + index_url: 'general/crontab/index', | ||
9 | + add_url: 'general/crontab/add', | ||
10 | + edit_url: 'general/crontab/edit', | ||
11 | + del_url: 'general/crontab/del', | ||
12 | + multi_url: 'general/crontab/multi', | ||
13 | + table: 'crontab' | ||
14 | + } | ||
15 | + }); | ||
16 | + | ||
17 | + var table = $("#table"); | ||
18 | + | ||
19 | + // 初始化表格 | ||
20 | + table.bootstrapTable({ | ||
21 | + url: $.fn.bootstrapTable.defaults.extend.index_url, | ||
22 | + sortName: 'weigh', | ||
23 | + columns: [ | ||
24 | + [ | ||
25 | + {field: 'state', checkbox: true,}, | ||
26 | + {field: 'id', title: 'ID'}, | ||
27 | + {field: 'type', title: __('Type'), searchList: Config.typeList, formatter: Table.api.formatter.label}, | ||
28 | + {field: 'title', title: __('Title')}, | ||
29 | + {field: 'maximums', title: __('Maximums'), formatter: Controller.api.formatter.maximums}, | ||
30 | + {field: 'executes', title: __('Executes')}, | ||
31 | + {field: 'begintime', title: __('Begin time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'}, | ||
32 | + {field: 'endtime', title: __('End time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'}, | ||
33 | + {field: 'nexttime', title: __('Next execute time'), formatter: Controller.api.formatter.nexttime, operate: false, addclass: 'datetimerange', sortable: true}, | ||
34 | + {field: 'executetime', title: __('Execute time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true}, | ||
35 | + {field: 'weigh', title: __('Weigh')}, | ||
36 | + {field: 'status', title: __('Status'), formatter: Table.api.formatter.status}, | ||
37 | + { | ||
38 | + field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate, | ||
39 | + buttons: [ | ||
40 | + { | ||
41 | + name: "detail", | ||
42 | + icon: "fa fa-list", | ||
43 | + title: function (row, index) { | ||
44 | + return __('Logs') + "[" + row['title'] + "]"; | ||
45 | + }, | ||
46 | + text: __('Logs'), | ||
47 | + classname: "btn btn-xs btn-info btn-dialog", | ||
48 | + url: "general/crontab_log/index?crontab_id={ids}", | ||
49 | + } | ||
50 | + ] | ||
51 | + } | ||
52 | + ] | ||
53 | + ] | ||
54 | + }); | ||
55 | + | ||
56 | + // 为表格绑定事件 | ||
57 | + Table.api.bindevent(table); | ||
58 | + }, | ||
59 | + add: function () { | ||
60 | + Controller.api.bindevent(); | ||
61 | + }, | ||
62 | + edit: function () { | ||
63 | + Controller.api.bindevent(); | ||
64 | + }, | ||
65 | + api: { | ||
66 | + bindevent: function () { | ||
67 | + $('#schedule').on('valid.field', function (e, result) { | ||
68 | + $("#pickdays").trigger("change"); | ||
69 | + }); | ||
70 | + Form.api.bindevent($("form[role=form]")); | ||
71 | + $(document).on("change", "#pickdays", function () { | ||
72 | + Fast.api.ajax({url: "general/crontab/get_schedule_future", data: {schedule: $("#schedule").val(), days: $(this).val()}}, function (data, ret) { | ||
73 | + if (typeof data.futuretime !== 'undefined' && $.isArray(data.futuretime)) { | ||
74 | + var result = []; | ||
75 | + $.each(data.futuretime, function (i, j) { | ||
76 | + result.push("<li class='list-group-item'>" + j + "<span class='badge'>" + (i + 1) + "</span></li>"); | ||
77 | + }); | ||
78 | + $("#scheduleresult").html(result.join("")); | ||
79 | + } else { | ||
80 | + $("#scheduleresult").html(""); | ||
81 | + } | ||
82 | + return false; | ||
83 | + }); | ||
84 | + }); | ||
85 | + $("#pickdays").trigger("change"); | ||
86 | + }, | ||
87 | + formatter: { | ||
88 | + nexttime: function (value, row, index) { | ||
89 | + if (isNaN(value)) { | ||
90 | + return value; | ||
91 | + } else { | ||
92 | + return Table.api.formatter.datetime.call(this, value, row, index); | ||
93 | + } | ||
94 | + }, | ||
95 | + maximums: function (value, row, index) { | ||
96 | + return value === 0 ? __('No limit') : value; | ||
97 | + } | ||
98 | + } | ||
99 | + } | ||
100 | + }; | ||
101 | + return Controller; | ||
102 | +}); |
1 | +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { | ||
2 | + | ||
3 | + var Controller = { | ||
4 | + index: function () { | ||
5 | + // 初始化表格参数配置 | ||
6 | + Table.api.init({ | ||
7 | + extend: { | ||
8 | + index_url: 'general/crontab_log/index', | ||
9 | + add_url: 'general/crontab_log/add', | ||
10 | + edit_url: '', | ||
11 | + del_url: 'general/crontab_log/del', | ||
12 | + multi_url: 'general/crontab_log/multi', | ||
13 | + table: 'crontab' | ||
14 | + } | ||
15 | + }); | ||
16 | + | ||
17 | + var table = $("#table"); | ||
18 | + | ||
19 | + // 初始化表格 | ||
20 | + table.bootstrapTable({ | ||
21 | + url: $.fn.bootstrapTable.defaults.extend.index_url, | ||
22 | + sortName: 'id', | ||
23 | + columns: [ | ||
24 | + [ | ||
25 | + {field: 'state', checkbox: true,}, | ||
26 | + {field: 'id', title: 'ID'}, | ||
27 | + {field: 'crontab_id', title: __('Crontab_id'), formatter: Table.api.formatter.search}, | ||
28 | + {field: 'executetime', title: __('Execute time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true}, | ||
29 | + {field: 'completetime', title: __('Complete time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true}, | ||
30 | + {field: 'status', title: __('Status'), searchList: Config.statusList, custom: {success: 'success', failure: 'danger'}, formatter: Table.api.formatter.status}, | ||
31 | + { | ||
32 | + field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate, | ||
33 | + buttons: [ | ||
34 | + { | ||
35 | + name: "detail", | ||
36 | + text: __("Result"), | ||
37 | + classname: "btn btn-xs btn-info btn-dialog", | ||
38 | + icon: "fa fa-file", | ||
39 | + url: "general/crontab_log/detail", | ||
40 | + extend: "data-window='parent'" | ||
41 | + } | ||
42 | + ] | ||
43 | + } | ||
44 | + ] | ||
45 | + ] | ||
46 | + }); | ||
47 | + | ||
48 | + // 为表格绑定事件 | ||
49 | + Table.api.bindevent(table); | ||
50 | + }, | ||
51 | + add: function () { | ||
52 | + Controller.api.bindevent(); | ||
53 | + }, | ||
54 | + edit: function () { | ||
55 | + Controller.api.bindevent(); | ||
56 | + }, | ||
57 | + api: { | ||
58 | + bindevent: function () { | ||
59 | + Form.api.bindevent($("form[role=form]")); | ||
60 | + | ||
61 | + }, | ||
62 | + } | ||
63 | + }; | ||
64 | + return Controller; | ||
65 | +}); |
1 | +<?php | ||
2 | + | ||
3 | +namespace app\admin\controller\general; | ||
4 | + | ||
5 | +use app\common\controller\Backend; | ||
6 | +use Cron\CronExpression; | ||
7 | + | ||
8 | +/** | ||
9 | + * 定时任务 | ||
10 | + * | ||
11 | + * @icon fa fa-tasks | ||
12 | + * @remark 类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行 | ||
13 | + */ | ||
14 | +class Crontab extends Backend | ||
15 | +{ | ||
16 | + | ||
17 | + protected $model = null; | ||
18 | + protected $noNeedRight = ['check_schedule', 'get_schedule_future']; | ||
19 | + | ||
20 | + public function _initialize() | ||
21 | + { | ||
22 | + parent::_initialize(); | ||
23 | + $this->model = model('Crontab'); | ||
24 | + $this->view->assign('typeList', \app\admin\model\Crontab::getTypeList()); | ||
25 | + $this->assignconfig('typeList', \app\admin\model\Crontab::getTypeList()); | ||
26 | + } | ||
27 | + | ||
28 | + /** | ||
29 | + * 查看 | ||
30 | + */ | ||
31 | + public function index() | ||
32 | + { | ||
33 | + if ($this->request->isAjax()) { | ||
34 | + list($where, $sort, $order, $offset, $limit) = $this->buildparams(); | ||
35 | + $total = $this->model | ||
36 | + ->where($where) | ||
37 | + ->order($sort, $order) | ||
38 | + ->count(); | ||
39 | + | ||
40 | + $list = $this->model | ||
41 | + ->where($where) | ||
42 | + ->order($sort, $order) | ||
43 | + ->limit($offset, $limit) | ||
44 | + ->select(); | ||
45 | + $time = time(); | ||
46 | + foreach ($list as $k => &$v) { | ||
47 | + $cron = CronExpression::factory($v['schedule']); | ||
48 | + $v['nexttime'] = $time > $v['endtime'] ? __('None') : $cron->getNextRunDate()->getTimestamp(); | ||
49 | + } | ||
50 | + $result = array("total" => $total, "rows" => $list); | ||
51 | + | ||
52 | + return json($result); | ||
53 | + } | ||
54 | + return $this->view->fetch(); | ||
55 | + } | ||
56 | + | ||
57 | + /** | ||
58 | + * 判断Crontab格式是否正确 | ||
59 | + * @internal | ||
60 | + */ | ||
61 | + public function check_schedule() | ||
62 | + { | ||
63 | + $row = $this->request->post("row/a"); | ||
64 | + $schedule = isset($row['schedule']) ? $row['schedule'] : ''; | ||
65 | + if (CronExpression::isValidExpression($schedule)) { | ||
66 | + $this->success(); | ||
67 | + } else { | ||
68 | + $this->error(__('Crontab format invalid')); | ||
69 | + } | ||
70 | + } | ||
71 | + | ||
72 | + /** | ||
73 | + * 根据Crontab表达式读取未来七次的时间 | ||
74 | + * @internal | ||
75 | + */ | ||
76 | + public function get_schedule_future() | ||
77 | + { | ||
78 | + $time = []; | ||
79 | + $schedule = $this->request->post('schedule'); | ||
80 | + $days = (int)$this->request->post('days'); | ||
81 | + try { | ||
82 | + $cron = CronExpression::factory($schedule); | ||
83 | + for ($i = 0; $i < $days; $i++) { | ||
84 | + $time[] = $cron->getNextRunDate(null, $i)->format('Y-m-d H:i:s'); | ||
85 | + } | ||
86 | + } catch (\Exception $e) { | ||
87 | + | ||
88 | + } | ||
89 | + | ||
90 | + $this->success("", null, ['futuretime' => $time]); | ||
91 | + } | ||
92 | + | ||
93 | +} |
1 | +<?php | ||
2 | + | ||
3 | +namespace app\admin\controller\general; | ||
4 | + | ||
5 | +use app\common\controller\Backend; | ||
6 | + | ||
7 | +/** | ||
8 | + * 定时任务 | ||
9 | + * | ||
10 | + * @icon fa fa-tasks | ||
11 | + * @remark 类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行 | ||
12 | + */ | ||
13 | +class CrontabLog extends Backend | ||
14 | +{ | ||
15 | + | ||
16 | + protected $model = null; | ||
17 | + | ||
18 | + public function _initialize() | ||
19 | + { | ||
20 | + parent::_initialize(); | ||
21 | + $this->model = model('CrontabLog'); | ||
22 | + $this->view->assign('statusList', $this->model->getStatusList()); | ||
23 | + $this->assignconfig('statusList', $this->model->getStatusList()); | ||
24 | + } | ||
25 | + | ||
26 | + /** | ||
27 | + * 查看 | ||
28 | + */ | ||
29 | + public function index() | ||
30 | + { | ||
31 | + if ($this->request->isAjax()) { | ||
32 | + list($where, $sort, $order, $offset, $limit) = $this->buildparams(); | ||
33 | + $total = $this->model | ||
34 | + ->where($where) | ||
35 | + ->order($sort, $order) | ||
36 | + ->count(); | ||
37 | + | ||
38 | + $list = $this->model | ||
39 | + ->where($where) | ||
40 | + ->order($sort, $order) | ||
41 | + ->limit($offset, $limit) | ||
42 | + ->select(); | ||
43 | + $list = collection($list)->toArray(); | ||
44 | + $result = array("total" => $total, "rows" => $list); | ||
45 | + | ||
46 | + return json($result); | ||
47 | + } | ||
48 | + return $this->view->fetch(); | ||
49 | + } | ||
50 | + | ||
51 | + public function detail($ids = null) | ||
52 | + { | ||
53 | + $row = $this->model->get($ids); | ||
54 | + if (!$row) { | ||
55 | + $this->error(__('No Results were found')); | ||
56 | + } | ||
57 | + $this->view->assign("row", $row); | ||
58 | + return $this->view->fetch(); | ||
59 | + } | ||
60 | + | ||
61 | +} |
1 | +<?php | ||
2 | + | ||
3 | +return [ | ||
4 | + 'Title' => '任务标题', | ||
5 | + 'Maximums' => '最多执行', | ||
6 | + 'Sleep' => '延迟秒数', | ||
7 | + 'Schedule' => '执行周期', | ||
8 | + 'Executes' => '执行次数', | ||
9 | + 'Completed' => '已完成', | ||
10 | + 'Expired' => '已过期', | ||
11 | + 'Hidden' => '已禁用', | ||
12 | + 'Logs' => '日志信息', | ||
13 | + 'Crontab rules' => 'Crontab规则', | ||
14 | + 'No limit' => '无限制', | ||
15 | + 'Execute time' => '最后执行时间', | ||
16 | + 'Request Url' => '请求URL', | ||
17 | + 'Execute Sql Script' => '执行SQL', | ||
18 | + 'Execute Shell' => '执行Shell', | ||
19 | + 'Crontab format invalid' => 'Crontab格式错误', | ||
20 | + 'Next execute time' => '下次预计时间', | ||
21 | + 'The next %s times the execution time' => '接下来 %s 次的执行时间', | ||
22 | +]; |
application/admin/model/Crontab.php
0 → 100644
1 | +<?php | ||
2 | + | ||
3 | +namespace app\admin\model; | ||
4 | + | ||
5 | +use think\Model; | ||
6 | + | ||
7 | +class Crontab extends Model | ||
8 | +{ | ||
9 | + | ||
10 | + // 开启自动写入时间戳字段 | ||
11 | + protected $autoWriteTimestamp = 'int'; | ||
12 | + // 定义时间戳字段名 | ||
13 | + protected $createTime = 'createtime'; | ||
14 | + protected $updateTime = 'updatetime'; | ||
15 | + // 定义字段类型 | ||
16 | + protected $type = [ | ||
17 | + ]; | ||
18 | + // 追加属性 | ||
19 | + protected $append = [ | ||
20 | + 'type_text' | ||
21 | + ]; | ||
22 | + | ||
23 | + public static function getTypeList() | ||
24 | + { | ||
25 | + return [ | ||
26 | + 'url' => __('Request Url'), | ||
27 | + 'sql' => __('Execute Sql Script'), | ||
28 | + 'shell' => __('Execute Shell'), | ||
29 | + ]; | ||
30 | + } | ||
31 | + | ||
32 | + public function getTypeTextAttr($value, $data) | ||
33 | + { | ||
34 | + $typelist = self::getTypeList(); | ||
35 | + $value = $value ? $value : $data['type']; | ||
36 | + return $value && isset($typelist[$value]) ? $typelist[$value] : $value; | ||
37 | + } | ||
38 | + | ||
39 | + protected function setBegintimeAttr($value) | ||
40 | + { | ||
41 | + return $value && !is_numeric($value) ? strtotime($value) : $value; | ||
42 | + } | ||
43 | + | ||
44 | + protected function setEndtimeAttr($value) | ||
45 | + { | ||
46 | + return $value && !is_numeric($value) ? strtotime($value) : $value; | ||
47 | + } | ||
48 | + | ||
49 | + protected function setExecutetimeAttr($value) | ||
50 | + { | ||
51 | + return $value && !is_numeric($value) ? strtotime($value) : $value; | ||
52 | + } | ||
53 | + | ||
54 | +} |
application/admin/model/CrontabLog.php
0 → 100644
1 | +<?php | ||
2 | + | ||
3 | +namespace app\admin\model; | ||
4 | + | ||
5 | +use think\Model; | ||
6 | + | ||
7 | +class CrontabLog extends Model | ||
8 | +{ | ||
9 | + | ||
10 | + // 开启自动写入时间戳字段 | ||
11 | + protected $autoWriteTimestamp = 'int'; | ||
12 | + // 定义时间戳字段名 | ||
13 | + protected $createTime = false; | ||
14 | + protected $updateTime = false; | ||
15 | + // 定义字段类型 | ||
16 | + protected $type = [ | ||
17 | + ]; | ||
18 | + // 追加属性 | ||
19 | + protected $append = [ | ||
20 | + ]; | ||
21 | + | ||
22 | + public function getStatusList() | ||
23 | + { | ||
24 | + return ['normal' => __('Normal'), 'hidden' => __('Hidden')]; | ||
25 | + } | ||
26 | + | ||
27 | + public function getStatusTextAttr($value, $data) | ||
28 | + { | ||
29 | + $value = $value ? $value : (isset($data['status']) ? $data['status'] : ''); | ||
30 | + $list = $this->getStatusList(); | ||
31 | + return isset($list[$value]) ? $list[$value] : ''; | ||
32 | + } | ||
33 | + | ||
34 | +} |
1 | +<style type="text/css"> | ||
2 | + #schedulepicker { | ||
3 | + padding-top:7px; | ||
4 | + } | ||
5 | +</style> | ||
6 | +<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action=""> | ||
7 | + <div class="form-group"> | ||
8 | + <label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label> | ||
9 | + <div class="col-xs-12 col-sm-8"> | ||
10 | + <input type="text" class="form-control" id="title" name="row[title]" value="" data-rule="required" /> | ||
11 | + </div> | ||
12 | + </div> | ||
13 | + <div class="form-group"> | ||
14 | + <label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label> | ||
15 | + <div class="col-xs-12 col-sm-8"> | ||
16 | + {:build_select('row[type]', $typeList, null, ['class'=>'form-control', 'data-rule'=>'required'])} | ||
17 | + </div> | ||
18 | + </div> | ||
19 | + <div class="form-group"> | ||
20 | + <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label> | ||
21 | + <div class="col-xs-12 col-sm-8"> | ||
22 | + <textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required"></textarea> | ||
23 | + </div> | ||
24 | + </div> | ||
25 | + <div class="form-group"> | ||
26 | + <label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label> | ||
27 | + <div class="col-xs-12 col-sm-8"> | ||
28 | + <div class="input-group margin-bottom-sm"> | ||
29 | + <input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="* * * * *" data-rule="required; remote(general/crontab/check_schedule)"/> | ||
30 | + <span class="input-group-btn"> | ||
31 | + <a href="https://www.fastadmin.net/store/crontab.html" target="_blank" class="btn btn-default"><i class="fa fa-info-circle"></i> {:__('Crontab rules')}</a> | ||
32 | + </span> | ||
33 | + <span class="msg-box n-right"></span> | ||
34 | + </div> | ||
35 | + <div id="schedulepicker"> | ||
36 | + <pre><code>* * * * * | ||
37 | +- - - - - | ||
38 | +| | | | +--- day of week (0 - 7) (Sunday=0 or 7) | ||
39 | +| | | +-------- month (1 - 12) | ||
40 | +| | +------------- day of month (1 - 31) | ||
41 | +| +------------------ hour (0 - 23) | ||
42 | ++----------------------- min (0 - 59)</code></pre> | ||
43 | + <h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5> | ||
44 | + <ol id="scheduleresult" class="list-group"> | ||
45 | + </ol> | ||
46 | + </div> | ||
47 | + </div> | ||
48 | + </div> | ||
49 | + <div class="form-group"> | ||
50 | + <label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label> | ||
51 | + <div class="col-xs-12 col-sm-4"> | ||
52 | + <input type="number" class="form-control" id="maximums" name="row[maximums]" value="0" data-rule="required" size="6" /> | ||
53 | + </div> | ||
54 | + </div> | ||
55 | + <div class="form-group"> | ||
56 | + <label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label> | ||
57 | + <div class="col-xs-12 col-sm-4"> | ||
58 | + <input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="" data-rule="{:__('Begin time')}:required" size="6" /> | ||
59 | + </div> | ||
60 | + </div> | ||
61 | + <div class="form-group"> | ||
62 | + <label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label> | ||
63 | + <div class="col-xs-12 col-sm-4"> | ||
64 | + <input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" /> | ||
65 | + </div> | ||
66 | + </div> | ||
67 | + <div class="form-group"> | ||
68 | + <label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label> | ||
69 | + <div class="col-xs-12 col-sm-4"> | ||
70 | + <input type="text" class="form-control" id="weigh" name="row[weigh]" value="0" data-rule="required" size="6" /> | ||
71 | + </div> | ||
72 | + </div> | ||
73 | + <div class="form-group"> | ||
74 | + <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label> | ||
75 | + <div class="col-xs-12 col-sm-8"> | ||
76 | + {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])} | ||
77 | + </div> | ||
78 | + </div> | ||
79 | + <div class="form-group hide layer-footer"> | ||
80 | + <label class="control-label col-xs-12 col-sm-2"></label> | ||
81 | + <div class="col-xs-12 col-sm-8"> | ||
82 | + <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button> | ||
83 | + <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button> | ||
84 | + </div> | ||
85 | + </div> | ||
86 | + | ||
87 | +</form> |
1 | +<style type="text/css"> | ||
2 | + #schedulepicker { | ||
3 | + padding-top: 7px; | ||
4 | + } | ||
5 | +</style> | ||
6 | +<form id="edit-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action=""> | ||
7 | + <div class="form-group"> | ||
8 | + <label for="name" class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label> | ||
9 | + <div class="col-xs-12 col-sm-8"> | ||
10 | + <input type="text" class="form-control" id="title" name="row[title]" value="{$row.title}" data-rule="required"/> | ||
11 | + </div> | ||
12 | + </div> | ||
13 | + <div class="form-group"> | ||
14 | + <label for="name" class="control-label col-xs-12 col-sm-2">{:__('Type')}:</label> | ||
15 | + <div class="col-xs-12 col-sm-8"> | ||
16 | + {:build_select('row[type]', $typeList, $row['type'], ['class'=>'form-control', 'data-rule'=>'required'])} | ||
17 | + </div> | ||
18 | + </div> | ||
19 | + <div class="form-group"> | ||
20 | + <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label> | ||
21 | + <div class="col-xs-12 col-sm-8"> | ||
22 | + <textarea name="row[content]" id="conent" cols="30" rows="5" class="form-control" data-rule="required">{$row.content}</textarea> | ||
23 | + </div> | ||
24 | + </div> | ||
25 | + <div class="form-group"> | ||
26 | + <label for="schedule" class="control-label col-xs-12 col-sm-2">{:__('Schedule')}:</label> | ||
27 | + <div class="col-xs-12 col-sm-8"> | ||
28 | + <div class="input-group margin-bottom-sm"> | ||
29 | + <input type="text" class="form-control" id="schedule" style="font-size:12px;font-family: Verdana;word-spacing:23px;" name="row[schedule]" value="{$row.schedule}" data-rule="required; remote(general/crontab/check_schedule)"/> | ||
30 | + <span class="input-group-btn"> | ||
31 | + <a href="https://www.fastadmin.net/store/crontab.html" target="_blank" class="btn btn-default"><i class="fa fa-info-circle"></i> {:__('Crontab rules')}</a> | ||
32 | + </span> | ||
33 | + <span class="msg-box n-right"></span> | ||
34 | + </div> | ||
35 | + | ||
36 | + <div id="schedulepicker"> | ||
37 | + <pre><code>* * * * * | ||
38 | +- - - - - | ||
39 | +| | | | +--- day of week (0 - 7) (Sunday=0 or 7) | ||
40 | +| | | +-------- month (1 - 12) | ||
41 | +| | +------------- day of month (1 - 31) | ||
42 | +| +------------------ hour (0 - 23) | ||
43 | ++----------------------- min (0 - 59)</code></pre> | ||
44 | + <h5>{:__('The next %s times the execution time', '<input type="number" id="pickdays" class="form-control text-center" value="7" style="display: inline-block;width:80px;">')}</h5> | ||
45 | + <ol id="scheduleresult" class="list-group"> | ||
46 | + </ol> | ||
47 | + </div> | ||
48 | + </div> | ||
49 | + </div> | ||
50 | + <div class="form-group"> | ||
51 | + <label for="maximums" class="control-label col-xs-12 col-sm-2">{:__('Maximums')}:</label> | ||
52 | + <div class="col-xs-12 col-sm-4"> | ||
53 | + <input type="number" class="form-control" id="maximums" name="row[maximums]" value="{$row.maximums}" data-rule="required" size="6"/> | ||
54 | + </div> | ||
55 | + </div> | ||
56 | + <div class="form-group"> | ||
57 | + <label for="begintime" class="control-label col-xs-12 col-sm-2">{:__('Begin time')}:</label> | ||
58 | + <div class="col-xs-12 col-sm-4"> | ||
59 | + <input type="text" class="form-control datetimepicker" id="begintime" name="row[begintime]" value="{$row.begintime|datetime}" data-rule="{:__('Begin time')}:required" size="6"/> | ||
60 | + </div> | ||
61 | + </div> | ||
62 | + <div class="form-group"> | ||
63 | + <label for="endtime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label> | ||
64 | + <div class="col-xs-12 col-sm-4"> | ||
65 | + <input type="text" class="form-control datetimepicker" id="endtime" name="row[endtime]" value="{$row.endtime|datetime}" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6"/> | ||
66 | + </div> | ||
67 | + </div> | ||
68 | + <div class="form-group"> | ||
69 | + <label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label> | ||
70 | + <div class="col-xs-12 col-sm-4"> | ||
71 | + <input type="text" class="form-control" id="weigh" name="row[weigh]" value="{$row.weigh}" data-rule="required" size="6"/> | ||
72 | + </div> | ||
73 | + </div> | ||
74 | + <div class="form-group"> | ||
75 | + <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label> | ||
76 | + <div class="col-xs-12 col-sm-8"> | ||
77 | + {:build_radios('row[status]', ['normal'=>__('Normal'), 'completed'=>__('Completed'), 'expired'=>__('Expired'), 'hidden'=>__('Hidden')], $row['status'])} | ||
78 | + </div> | ||
79 | + </div> | ||
80 | + <div class="form-group hide layer-footer"> | ||
81 | + <label class="control-label col-xs-12 col-sm-2"></label> | ||
82 | + <div class="col-xs-12 col-sm-8"> | ||
83 | + <button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button> | ||
84 | + <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button> | ||
85 | + </div> | ||
86 | + </div> | ||
87 | + | ||
88 | +</form> |
1 | +<div class="panel panel-default panel-intro"> | ||
2 | + | ||
3 | + <div class="panel-heading"> | ||
4 | + {:build_heading(null,FALSE)} | ||
5 | + <ul class="nav nav-tabs" data-field="type"> | ||
6 | + <li class="active"><a href="#t-all" data-value="" data-toggle="tab">{:__('All')}</a></li> | ||
7 | + {foreach name="typeList" item="vo"} | ||
8 | + <li><a href="#t-{$key}" data-value="{$key}" data-toggle="tab">{$vo}</a></li> | ||
9 | + {/foreach} | ||
10 | + </ul> | ||
11 | + </div> | ||
12 | + | ||
13 | + <div class="panel-body"> | ||
14 | + <div id="myTabContent" class="tab-content"> | ||
15 | + <div class="tab-pane fade active in" id="one"> | ||
16 | + <div class="widget-body no-padding"> | ||
17 | + <div id="toolbar" class="toolbar"> | ||
18 | + {:build_toolbar('refresh,add,edit,del')} | ||
19 | + </div> | ||
20 | + <table id="table" class="table table-striped table-bordered table-hover" | ||
21 | + data-operate-edit="{:$auth->check('general/crontab/edit')}" | ||
22 | + data-operate-del="{:$auth->check('general/crontab/del')}" | ||
23 | + width="100%"> | ||
24 | + </table> | ||
25 | + </div> | ||
26 | + </div> | ||
27 | + | ||
28 | + </div> | ||
29 | + </div> | ||
30 | +</div> |
1 | +<style type="text/css"> | ||
2 | + #schedulepicker { | ||
3 | + padding-top:7px; | ||
4 | + } | ||
5 | +</style> | ||
6 | +<form id="edit-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action=""> | ||
7 | + <div class="form-group"> | ||
8 | + <label for="content" class="control-label col-xs-12 col-sm-2">{:__('Content')}:</label> | ||
9 | + <div class="col-xs-12 col-sm-10"> | ||
10 | + <textarea name="row[content]" id="conent" cols="30" style="width:100%;" rows="20" class="form-control" data-rule="required" readonly>{$row.content|htmlentities}</textarea> | ||
11 | + </div> | ||
12 | + </div> | ||
13 | + <div class="form-group"> | ||
14 | + <label for="executetime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label> | ||
15 | + <div class="col-xs-12 col-sm-4"> | ||
16 | + <input type="text" class="form-control datetimepicker" id="executetime" name="row[executetime]" value="{$row.executetime|datetime}" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" disabled /> | ||
17 | + </div> | ||
18 | + </div> | ||
19 | + <div class="form-group"> | ||
20 | + <label for="completetime" class="control-label col-xs-12 col-sm-2">{:__('End time')}:</label> | ||
21 | + <div class="col-xs-12 col-sm-4"> | ||
22 | + <input type="text" class="form-control datetimepicker" id="completetime" name="row[completetime]" value="{$row.completetime|datetime}" data-rule="{:__('End time')}:required;match(gte, row[begintime], datetime)" size="6" disabled /> | ||
23 | + </div> | ||
24 | + </div> | ||
25 | + <div class="form-group"> | ||
26 | + <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label> | ||
27 | + <div class="col-xs-12 col-sm-8"> | ||
28 | + <div style="padding-top:8px;"> | ||
29 | + {if $row['status']=='success'}<span class="label label-success">{:__('Success')}</span>{else/}<span class="label label-danger">{:__('Failure')}</span>{/if} | ||
30 | + </div> | ||
31 | + </div> | ||
32 | + </div> | ||
33 | + <div class="form-group hide layer-footer"> | ||
34 | + <label class="control-label col-xs-12 col-sm-2"></label> | ||
35 | + <div class="col-xs-12 col-sm-8"> | ||
36 | + <button type="button" class="btn btn-success btn-embossed" onclick="parent.Layer.close(parent.Layer.getFrameIndex(window.name))">{:__('Close')}</button> | ||
37 | + </div> | ||
38 | + </div> | ||
39 | + | ||
40 | +</form> |
1 | +<div class="panel panel-default panel-intro"> | ||
2 | + | ||
3 | + <div class="panel-heading"> | ||
4 | + {:build_heading(null,FALSE)} | ||
5 | + <ul class="nav nav-tabs" data-field="status"> | ||
6 | + <li class="active"><a href="#t-all" data-value="" data-toggle="tab">{:__('All')}</a></li> | ||
7 | + {foreach name="statusList" item="vo"} | ||
8 | + <li><a href="#t-{$key}" data-value="{$key}" data-toggle="tab">{$vo}</a></li> | ||
9 | + {/foreach} | ||
10 | + </ul> | ||
11 | + </div> | ||
12 | + | ||
13 | + <div class="panel-body"> | ||
14 | + <div id="myTabContent" class="tab-content"> | ||
15 | + <div class="tab-pane fade active in" id="one"> | ||
16 | + <div class="widget-body no-padding"> | ||
17 | + <div id="toolbar" class="toolbar"> | ||
18 | + {:build_toolbar('refresh,del')} | ||
19 | + </div> | ||
20 | + <table id="table" class="table table-striped table-bordered table-hover" | ||
21 | + data-operate-detail="{:$auth->check('general/crontab/detail')}" | ||
22 | + data-operate-del="{:$auth->check('general/crontab/del')}" | ||
23 | + width="100%"> | ||
24 | + </table> | ||
25 | + </div> | ||
26 | + </div> | ||
27 | + | ||
28 | + </div> | ||
29 | + </div> | ||
30 | +</div> |
此 diff 太大无法显示。
public/assets/js/backend/general/crontab.js
0 → 100644
1 | +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { | ||
2 | + | ||
3 | + var Controller = { | ||
4 | + index: function () { | ||
5 | + // 初始化表格参数配置 | ||
6 | + Table.api.init({ | ||
7 | + extend: { | ||
8 | + index_url: 'general/crontab/index', | ||
9 | + add_url: 'general/crontab/add', | ||
10 | + edit_url: 'general/crontab/edit', | ||
11 | + del_url: 'general/crontab/del', | ||
12 | + multi_url: 'general/crontab/multi', | ||
13 | + table: 'crontab' | ||
14 | + } | ||
15 | + }); | ||
16 | + | ||
17 | + var table = $("#table"); | ||
18 | + | ||
19 | + // 初始化表格 | ||
20 | + table.bootstrapTable({ | ||
21 | + url: $.fn.bootstrapTable.defaults.extend.index_url, | ||
22 | + sortName: 'weigh', | ||
23 | + columns: [ | ||
24 | + [ | ||
25 | + {field: 'state', checkbox: true,}, | ||
26 | + {field: 'id', title: 'ID'}, | ||
27 | + {field: 'type', title: __('Type'), searchList: Config.typeList, formatter: Table.api.formatter.label}, | ||
28 | + {field: 'title', title: __('Title')}, | ||
29 | + {field: 'maximums', title: __('Maximums'), formatter: Controller.api.formatter.maximums}, | ||
30 | + {field: 'executes', title: __('Executes')}, | ||
31 | + {field: 'begintime', title: __('Begin time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'}, | ||
32 | + {field: 'endtime', title: __('End time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange'}, | ||
33 | + {field: 'nexttime', title: __('Next execute time'), formatter: Controller.api.formatter.nexttime, operate: false, addclass: 'datetimerange', sortable: true}, | ||
34 | + {field: 'executetime', title: __('Execute time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true}, | ||
35 | + {field: 'weigh', title: __('Weigh')}, | ||
36 | + {field: 'status', title: __('Status'), formatter: Table.api.formatter.status}, | ||
37 | + { | ||
38 | + field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate, | ||
39 | + buttons: [ | ||
40 | + { | ||
41 | + name: "detail", | ||
42 | + icon: "fa fa-list", | ||
43 | + title: function (row, index) { | ||
44 | + return __('Logs') + "[" + row['title'] + "]"; | ||
45 | + }, | ||
46 | + text: __('Logs'), | ||
47 | + classname: "btn btn-xs btn-info btn-dialog", | ||
48 | + url: "general/crontab_log/index?crontab_id={ids}", | ||
49 | + } | ||
50 | + ] | ||
51 | + } | ||
52 | + ] | ||
53 | + ] | ||
54 | + }); | ||
55 | + | ||
56 | + // 为表格绑定事件 | ||
57 | + Table.api.bindevent(table); | ||
58 | + }, | ||
59 | + add: function () { | ||
60 | + Controller.api.bindevent(); | ||
61 | + }, | ||
62 | + edit: function () { | ||
63 | + Controller.api.bindevent(); | ||
64 | + }, | ||
65 | + api: { | ||
66 | + bindevent: function () { | ||
67 | + $('#schedule').on('valid.field', function (e, result) { | ||
68 | + $("#pickdays").trigger("change"); | ||
69 | + }); | ||
70 | + Form.api.bindevent($("form[role=form]")); | ||
71 | + $(document).on("change", "#pickdays", function () { | ||
72 | + Fast.api.ajax({url: "general/crontab/get_schedule_future", data: {schedule: $("#schedule").val(), days: $(this).val()}}, function (data, ret) { | ||
73 | + if (typeof data.futuretime !== 'undefined' && $.isArray(data.futuretime)) { | ||
74 | + var result = []; | ||
75 | + $.each(data.futuretime, function (i, j) { | ||
76 | + result.push("<li class='list-group-item'>" + j + "<span class='badge'>" + (i + 1) + "</span></li>"); | ||
77 | + }); | ||
78 | + $("#scheduleresult").html(result.join("")); | ||
79 | + } else { | ||
80 | + $("#scheduleresult").html(""); | ||
81 | + } | ||
82 | + return false; | ||
83 | + }); | ||
84 | + }); | ||
85 | + $("#pickdays").trigger("change"); | ||
86 | + }, | ||
87 | + formatter: { | ||
88 | + nexttime: function (value, row, index) { | ||
89 | + if (isNaN(value)) { | ||
90 | + return value; | ||
91 | + } else { | ||
92 | + return Table.api.formatter.datetime.call(this, value, row, index); | ||
93 | + } | ||
94 | + }, | ||
95 | + maximums: function (value, row, index) { | ||
96 | + return value === 0 ? __('No limit') : value; | ||
97 | + } | ||
98 | + } | ||
99 | + } | ||
100 | + }; | ||
101 | + return Controller; | ||
102 | +}); |
1 | +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { | ||
2 | + | ||
3 | + var Controller = { | ||
4 | + index: function () { | ||
5 | + // 初始化表格参数配置 | ||
6 | + Table.api.init({ | ||
7 | + extend: { | ||
8 | + index_url: 'general/crontab_log/index', | ||
9 | + add_url: 'general/crontab_log/add', | ||
10 | + edit_url: '', | ||
11 | + del_url: 'general/crontab_log/del', | ||
12 | + multi_url: 'general/crontab_log/multi', | ||
13 | + table: 'crontab' | ||
14 | + } | ||
15 | + }); | ||
16 | + | ||
17 | + var table = $("#table"); | ||
18 | + | ||
19 | + // 初始化表格 | ||
20 | + table.bootstrapTable({ | ||
21 | + url: $.fn.bootstrapTable.defaults.extend.index_url, | ||
22 | + sortName: 'id', | ||
23 | + columns: [ | ||
24 | + [ | ||
25 | + {field: 'state', checkbox: true,}, | ||
26 | + {field: 'id', title: 'ID'}, | ||
27 | + {field: 'crontab_id', title: __('Crontab_id'), formatter: Table.api.formatter.search}, | ||
28 | + {field: 'executetime', title: __('Execute time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true}, | ||
29 | + {field: 'completetime', title: __('Complete time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true}, | ||
30 | + {field: 'status', title: __('Status'), searchList: Config.statusList, custom: {success: 'success', failure: 'danger'}, formatter: Table.api.formatter.status}, | ||
31 | + { | ||
32 | + field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate, | ||
33 | + buttons: [ | ||
34 | + { | ||
35 | + name: "detail", | ||
36 | + text: __("Result"), | ||
37 | + classname: "btn btn-xs btn-info btn-dialog", | ||
38 | + icon: "fa fa-file", | ||
39 | + url: "general/crontab_log/detail", | ||
40 | + extend: "data-window='parent'" | ||
41 | + } | ||
42 | + ] | ||
43 | + } | ||
44 | + ] | ||
45 | + ] | ||
46 | + }); | ||
47 | + | ||
48 | + // 为表格绑定事件 | ||
49 | + Table.api.bindevent(table); | ||
50 | + }, | ||
51 | + add: function () { | ||
52 | + Controller.api.bindevent(); | ||
53 | + }, | ||
54 | + edit: function () { | ||
55 | + Controller.api.bindevent(); | ||
56 | + }, | ||
57 | + api: { | ||
58 | + bindevent: function () { | ||
59 | + Form.api.bindevent($("form[role=form]")); | ||
60 | + | ||
61 | + }, | ||
62 | + } | ||
63 | + }; | ||
64 | + return Controller; | ||
65 | +}); |
-
请 注册 或 登录 后发表评论