mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-01 15:32:48 +08:00
feat(timer): 新增定时器配置、日志和主机的后台管理界面
T10: TimerConfig CURD - task management with run_type/status editing,
manual trigger button, task_name read-only, no add/delete
T11: TimerLog CURD - read-only log viewer with filters and color badges
T12: Host list enhanced - is_master column, setMaster button
This commit is contained in:
97
app/admin/controller/system/TimerConfig.php
Normal file
97
app/admin/controller/system/TimerConfig.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="定时任务协调配置表")
|
||||
*/
|
||||
class TimerConfig extends AdminController
|
||||
{
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
|
||||
$this->model = new \app\admin\model\SystemTimerConfig();
|
||||
|
||||
$this->assign('select_list_run_type', $this->model::SELECT_LIST_RUN_TYPE, true);
|
||||
$this->assign('select_list_status', $this->model::SELECT_LIST_STATUS, true);
|
||||
$this->assign('select_list_is_synced', $this->model::SELECT_LIST_IS_SYNCED, true);
|
||||
$this->assign('select_list_manual_trigger', $this->model::SELECT_LIST_MANUAL_TRIGGER, true);
|
||||
|
||||
// 允许通过行内修改的字段
|
||||
$this->allowModifyFields = [
|
||||
'status',
|
||||
'run_type',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="添加")
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$this->error('定时任务配置由系统同步生成,不支持手动添加');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="删除")
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
$this->error('定时任务配置由系统管理,不支持删除');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="编辑")
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
$row = $this->model->find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
if ($this->request->isPost()) {
|
||||
$post = $this->request->post();
|
||||
// 只允许修改 run_type 和 status
|
||||
$post = array_intersect_key($post, array_flip(['run_type', 'status']));
|
||||
try {
|
||||
$save = $row->save($post);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('保存失败:' . $e->getMessage());
|
||||
}
|
||||
$save ? $this->success('保存成功') : $this->error('保存失败');
|
||||
}
|
||||
$this->assign('row', $row);
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="手动触发")
|
||||
*/
|
||||
public function trigger($id)
|
||||
{
|
||||
$this->checkPostRequest();
|
||||
$row = $this->model->find($id);
|
||||
if (!$row) {
|
||||
$this->error('数据不存在');
|
||||
}
|
||||
if ($row->getAttr('run_type') !== 'manual') {
|
||||
$this->error('只有manual类型的任务支持手动触发');
|
||||
}
|
||||
if ($row->getAttr('status') != 1) {
|
||||
$this->error('任务已停用,请先启用');
|
||||
}
|
||||
try {
|
||||
$row->save(['manual_trigger' => 1]);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('触发失败:' . $e->getMessage());
|
||||
}
|
||||
$this->success('触发成功,等待定时器执行');
|
||||
}
|
||||
}
|
||||
109
app/admin/controller/system/TimerLog.php
Normal file
109
app/admin/controller/system/TimerLog.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller\system;
|
||||
|
||||
use app\common\controller\AdminController;
|
||||
use app\admin\service\annotation\ControllerAnnotation;
|
||||
use app\admin\service\annotation\NodeAnotation;
|
||||
use think\App;
|
||||
|
||||
/**
|
||||
* @ControllerAnnotation(title="定时器执行日志")
|
||||
*/
|
||||
class TimerLog extends AdminController
|
||||
{
|
||||
|
||||
use \app\admin\traits\Curd;
|
||||
|
||||
protected $sort = [
|
||||
'id' => 'desc',
|
||||
];
|
||||
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
|
||||
$this->model = new \app\admin\model\SystemTimerLog();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="列表")
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
if (input('selectFields')) {
|
||||
return $this->selectList();
|
||||
}
|
||||
list($page, $limit, $where, $excludes, $request_options, $group) = $this->buildTableParames();
|
||||
$count = $this->model
|
||||
->where($where)
|
||||
->group($group)
|
||||
->count();
|
||||
$list = $this->model
|
||||
->where($where)
|
||||
->page($page, $limit)
|
||||
->order($this->sort)
|
||||
->group($group)
|
||||
->select();
|
||||
$data = [
|
||||
'code' => 0,
|
||||
'msg' => '',
|
||||
'count' => $count,
|
||||
'data' => $list,
|
||||
];
|
||||
|
||||
return json($data);
|
||||
}
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="详情")
|
||||
*/
|
||||
public function read($id)
|
||||
{
|
||||
$row = $this->model->find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
|
||||
$title = $row->title;
|
||||
|
||||
$this->assign('row', $row);
|
||||
$this->assign('title', $title);
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="添加")
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$this->error('日志表不允许手动添加');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="编辑")
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
$this->error('日志表不允许编辑');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="删除")
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
$this->error('日志表不允许删除');
|
||||
}
|
||||
|
||||
/**
|
||||
* @NodeAnotation(title="属性修改")
|
||||
*/
|
||||
public function modify()
|
||||
{
|
||||
$this->error('日志表不允许修改');
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,36 @@
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use think\Model;
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemTimerConfig extends Model
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $task_name 任务名称
|
||||
* @property string $run_type 运行类型:main/auto/all/manual main:main,auto:auto,all:all,manual:manual
|
||||
* @property int $status 状态:0=停用,1=启用 0:停用,1:启用
|
||||
* @property int $is_synced 是否已同步:0=未同步,1=已同步 0:未同步,1:已同步
|
||||
* @property string $last_execute_node 最后执行节点ID
|
||||
* @property int $last_execute_time 最后执行时间戳
|
||||
* @property int $manual_trigger 手动触发标记:0=未触发,1=已触发 0:未触发,1:已触发
|
||||
* @property int $create_time 创建时间
|
||||
*/
|
||||
class SystemTimerConfig extends TimeModel
|
||||
{
|
||||
protected $name = 'system_timer_config';
|
||||
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $name = "system_timer_config";
|
||||
|
||||
protected $deleteTime = false;
|
||||
|
||||
|
||||
public const SELECT_LIST_RUN_TYPE = ['main' => 'main', 'auto' => 'auto', 'all' => 'all', 'manual' => 'manual'];
|
||||
|
||||
public const SELECT_LIST_STATUS = ['0' => '停用', '1' => '启用'];
|
||||
|
||||
public const SELECT_LIST_IS_SYNCED = ['0' => '未同步', '1' => '已同步'];
|
||||
|
||||
public const SELECT_LIST_MANUAL_TRIGGER = ['0' => '未触发', '1' => '已触发'];
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,11 +2,30 @@
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use think\Model;
|
||||
use app\common\model\TimeModel;
|
||||
|
||||
class SystemTimerLog extends Model
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $task_name 任务名称
|
||||
* @property string $node_id 执行节点ID
|
||||
* @property string $run_type 运行类型
|
||||
* @property int $start_time 开始时间戳
|
||||
* @property int $end_time 结束时间戳
|
||||
* @property int $duration 耗时
|
||||
* @property string $status 状态:running/success/error
|
||||
* @property string $error_message 错误信息
|
||||
* @property int $concurrency_id 并发分片ID
|
||||
* @property int $create_time 创建时间
|
||||
*/
|
||||
class SystemTimerLog extends TimeModel
|
||||
{
|
||||
protected $name = 'system_timer_log';
|
||||
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $name = "system_timer_log";
|
||||
|
||||
protected $deleteTime = false;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
12
app/admin/view/system/timer_config/_common.js
Normal file
12
app/admin/view/system/timer_config/_common.js
Normal file
@@ -0,0 +1,12 @@
|
||||
var init = {
|
||||
tableElem: '#currentTable',
|
||||
tableRenderId: 'currentTableRenderId',
|
||||
indexUrl: 'system.timer_config/index',
|
||||
addUrl: 'system.timer_config/add' + location.search,
|
||||
editUrl: 'system.timer_config/edit',
|
||||
readUrl: 'system.timer_config/read',
|
||||
deleteUrl: 'system.timer_config/delete',
|
||||
exportUrl: 'system.timer_config/export',
|
||||
modifyUrl: 'system.timer_config/modify',
|
||||
triggerUrl: 'system.timer_config/trigger',
|
||||
};
|
||||
70
app/admin/view/system/timer_config/add.html
Normal file
70
app/admin/view/system/timer_config/add.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<div class="layuimini-container">
|
||||
<form id="app-form" class="layui-form layuimini-form">
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">任务名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="task_name" class="layui-input" lay-verify="required" placeholder="请输入任务名称" value="{$Request.param.task_name|default=''}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">运行类型:main/auto/all/manual</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="run_type" >
|
||||
<option value=''></option>
|
||||
{foreach $select_list_run_type as $k=>$v}
|
||||
<option value='{$k}' {in name="k" value="$Request.param.run_type"}selected=""{/in}>{$v}</option>
|
||||
{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态:0=停用,1=启用</label>
|
||||
<div class="layui-input-block">
|
||||
{foreach $select_list_status as $k=>$v}
|
||||
<input type="radio" name="status" value="{$k}" title="{$v}" {in name="k" value="$Request.param.status"}checked=""{/in}>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">是否已同步:0=未同步,1=已同步</label>
|
||||
<div class="layui-input-block">
|
||||
{foreach $select_list_is_synced as $k=>$v}
|
||||
<input type="radio" name="is_synced" value="{$k}" title="{$v}" {in name="k" value="$Request.param.is_synced"}checked=""{/in}>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">最后执行节点ID</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="last_execute_node" class="layui-input" placeholder="请输入最后执行节点ID" value="{$Request.param.last_execute_node|default=''}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">最后执行时间戳</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="last_execute_time" data-date="" data-date-type="datetime" class="layui-input" placeholder="请输入最后执行时间戳" value="{$Request.param.last_execute_time|default='0'}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">手动触发标记:0=未触发,1=已触发</label>
|
||||
<div class="layui-input-block">
|
||||
{foreach $select_list_manual_trigger as $k=>$v}
|
||||
<input type="radio" name="manual_trigger" value="{$k}" title="{$v}" {in name="k" value="$Request.param.manual_trigger"}checked=""{/in}>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line"></div>
|
||||
<div class="layui-form-item text-center">
|
||||
{notempty name='$Request.param.backTagId'}
|
||||
<div class="layui-btn layui-btn-sm page-back-button" layuimini-content-href="{$Request.param.backTagId}" data-back="1">返回</div>
|
||||
{/notempty}
|
||||
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
3
app/admin/view/system/timer_config/add.js
Normal file
3
app/admin/view/system/timer_config/add.js
Normal file
@@ -0,0 +1,3 @@
|
||||
$(function(){
|
||||
ua.listen();
|
||||
})
|
||||
69
app/admin/view/system/timer_config/edit.html
Normal file
69
app/admin/view/system/timer_config/edit.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<div class="layuimini-container">
|
||||
<form id="app-form" class="layui-form layuimini-form">
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">任务名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="task_name" class="layui-input layui-disabled" disabled value="{$row.task_name|default=''}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">运行类型</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="run_type" lay-verify="required">
|
||||
<option value=''></option>
|
||||
{foreach $select_list_run_type as $k=>$v}
|
||||
<option value='{$k}' {in name="k" value="$row.run_type"}selected=""{/in}>{$v}</option>
|
||||
{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-block">
|
||||
{foreach $select_list_status as $k=>$v}
|
||||
<input type="radio" name="status" value="{$k}" title="{$v}" {in name="k" value="$row.status"}checked=""{/in}>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">同步状态</label>
|
||||
<div class="layui-input-block" style="padding-top: 8px;">
|
||||
{if $row.is_synced == 1}
|
||||
<span class="layui-badge layui-bg-green">已同步</span>
|
||||
{else/}
|
||||
<span class="layui-badge">未同步</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">最后执行节点</label>
|
||||
<div class="layui-input-block" style="padding-top: 8px;">
|
||||
{$row.last_execute_node|default='<span class="layui-text-em">暂无</span>'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">最后执行时间</label>
|
||||
<div class="layui-input-block" style="padding-top: 8px;">
|
||||
{notempty name="row.last_execute_time"}
|
||||
{$row.last_execute_time|date="Y-m-d H:i:s"}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hr-line"></div>
|
||||
<div class="layui-form-item text-center">
|
||||
{notempty name='$Request.param.backTagId'}
|
||||
<div class="layui-btn layui-btn-sm page-back-button" layuimini-content-href="{$Request.param.backTagId}" data-back="1">返回</div>
|
||||
{/notempty}
|
||||
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
3
app/admin/view/system/timer_config/edit.js
Normal file
3
app/admin/view/system/timer_config/edit.js
Normal file
@@ -0,0 +1,3 @@
|
||||
$(function(){
|
||||
ua.listen();
|
||||
})
|
||||
11
app/admin/view/system/timer_config/index.html
Normal file
11
app/admin/view/system/timer_config/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="layuimini-container">
|
||||
<div class="layuimini-main">
|
||||
<table id="currentTable" class="layui-table layui-hide"
|
||||
data-auth-index="{:auth('system.timer_config/index')}"
|
||||
data-auth-edit="{:auth('system.timer_config/edit')}"
|
||||
data-auth-export="{:auth('system.timer_config/export')}"
|
||||
data-auth-modify="{:auth('system.timer_config/modify')}"
|
||||
lay-filter="currentTable">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
78
app/admin/view/system/timer_config/index.js
Normal file
78
app/admin/view/system/timer_config/index.js
Normal file
@@ -0,0 +1,78 @@
|
||||
$(function(){
|
||||
ua.table.render({
|
||||
init: init,
|
||||
cols: [[
|
||||
{type: 'checkbox'},
|
||||
{field: 'id', title: 'ID', width: 80},
|
||||
{field: 'task_name', title: '任务名称', minWidth: 160},
|
||||
{field: 'run_type', search: 'select', selectList: ua.getDataBrage('select_list_run_type'), title: '运行类型', width: 130, templet: function(d){
|
||||
var map = ua.getDataBrage('select_list_run_type');
|
||||
return map[d.run_type] || d.run_type;
|
||||
}},
|
||||
{field: 'status', search: 'select', selectList: ua.getDataBrage('select_list_status'), title: '状态', width: 100, templet: ua.table.switch},
|
||||
{field: 'is_synced', search: 'select', selectList: ua.getDataBrage('select_list_is_synced'), title: '同步状态', width: 100, templet: function(d){
|
||||
if(d.is_synced == 1){
|
||||
return '<span class="layui-badge layui-bg-green">已同步</span>';
|
||||
}
|
||||
return '<span class="layui-badge">未同步</span>';
|
||||
}},
|
||||
{field: 'last_execute_node', title: '最后执行节点', width: 140},
|
||||
{field: 'last_execute_time', title: '最后执行时间', width: 170, templet: function(d){
|
||||
if(!d.last_execute_time || d.last_execute_time == 0){
|
||||
return '<span class="layui-text-em">暂无</span>';
|
||||
}
|
||||
var date = new Date(d.last_execute_time * 1000);
|
||||
var Y = date.getFullYear();
|
||||
var m = ('0' + (date.getMonth()+1)).slice(-2);
|
||||
var day = ('0' + date.getDate()).slice(-2);
|
||||
var H = ('0' + date.getHours()).slice(-2);
|
||||
var i = ('0' + date.getMinutes()).slice(-2);
|
||||
var s = ('0' + date.getSeconds()).slice(-2);
|
||||
return Y+'-'+m+'-'+day+' '+H+':'+i+':'+s;
|
||||
}},
|
||||
{
|
||||
width: 200, title: '操作', templet: ua.table.tool, fixed: 'right', operat: [
|
||||
[{
|
||||
class: 'layui-btn layui-btn-primary layui-btn-xs',
|
||||
method: 'tab',
|
||||
field: 'id',
|
||||
text: '详情',
|
||||
title: '查看详情',
|
||||
auth: 'read',
|
||||
url: init.readUrl,
|
||||
icon: ''
|
||||
}],
|
||||
'edit',
|
||||
[{
|
||||
class: 'layui-btn layui-btn-warm layui-btn-xs',
|
||||
method: 'request',
|
||||
field: 'id',
|
||||
text: '触发',
|
||||
title: '手动触发',
|
||||
auth: 'trigger',
|
||||
url: init.triggerUrl,
|
||||
icon: '',
|
||||
extend: '',
|
||||
callback: function(o){
|
||||
// 只对 manual 类型显示,通过 CSS 控制显隐
|
||||
}
|
||||
}]
|
||||
]
|
||||
},
|
||||
|
||||
]],
|
||||
});
|
||||
|
||||
// 控制触发按钮显隐:只有 run_type=manual 时显示
|
||||
ua.listen();
|
||||
|
||||
// 监听表格渲染完成后处理触发按钮显隐
|
||||
$(document).on('renderComplete', function(){
|
||||
$('table tbody tr').each(function(){
|
||||
var runType = $(this).find('td[data-field="run_type"] .layui-table-cell').text().trim();
|
||||
if(runType !== 'manual'){
|
||||
$(this).find('.layui-btn[title="手动触发"]').hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
103
app/admin/view/system/timer_config/read.html
Normal file
103
app/admin/view/system/timer_config/read.html
Normal file
@@ -0,0 +1,103 @@
|
||||
<div class="layuimini-container detail-container">
|
||||
<div class="layuimini-main">
|
||||
<div class="layui-card detail-card">
|
||||
<div class="layui-card-header detail-header">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-md9">
|
||||
<h2 class="detail-title">#{$row.id} {$title}</h2>
|
||||
<div class="detail-id">ID: {$row.id}</div>
|
||||
</div>
|
||||
<div class="layui-col-md3 text-right detail-actions">
|
||||
<button class="layui-btn layui-btn-primary" layuimini-content-href="{$Request.param.backTagId}" data-back="1">返回</button>
|
||||
<button class="layui-btn" onclick="location.href='{:url("edit", ["id" => $row.id])}'">编辑</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-card-body detail-content">
|
||||
<div class="layui-row layui-col-space12">
|
||||
<div class="layui-col-md8 detail-main">
|
||||
<div class="detail-field-group">
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">任务名称</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.task_name"}
|
||||
{$row.task_name}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">运行类型</div>
|
||||
<div class="detail-field-value">
|
||||
{$select_list_run_type[$row.run_type]|default=''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">同步状态</div>
|
||||
<div class="detail-field-value">
|
||||
{if $row.is_synced == 1}
|
||||
<span class="layui-badge layui-bg-green">已同步</span>
|
||||
{else/}
|
||||
<span class="layui-badge">未同步</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">最后执行节点</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.last_execute_node"}
|
||||
{$row.last_execute_node}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">最后执行时间</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.last_execute_time"}
|
||||
{$row.last_execute_time|date="Y-m-d H:i:s"}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">手动触发标记</div>
|
||||
<div class="detail-field-value">
|
||||
<span class="layui-badge">{$select_list_manual_trigger[$row.manual_trigger]|default=''}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-md4 detail-side">
|
||||
<h3 class="detail-side-title">基础信息</h3>
|
||||
<div class="detail-field-group">
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">ID</div>
|
||||
<div class="detail-field-value">{$row.id}</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">状态</div>
|
||||
<div class="detail-field-value">
|
||||
<span class="layui-badge">{$select_list_status[$row.status]|default=''}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">创建时间</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.create_time"}
|
||||
{$row.create_time|date="Y-m-d H:i:s"}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
3
app/admin/view/system/timer_config/read.js
Normal file
3
app/admin/view/system/timer_config/read.js
Normal file
@@ -0,0 +1,3 @@
|
||||
$(function(){
|
||||
ua.listen();
|
||||
})
|
||||
11
app/admin/view/system/timer_log/_common.js
Normal file
11
app/admin/view/system/timer_log/_common.js
Normal file
@@ -0,0 +1,11 @@
|
||||
var init = {
|
||||
tableElem: '#currentTable',
|
||||
tableRenderId: 'currentTableRenderId',
|
||||
indexUrl: 'system.timer_log/index',
|
||||
addUrl: '',
|
||||
editUrl: '',
|
||||
readUrl: 'system.timer_log/read',
|
||||
deleteUrl: '',
|
||||
exportUrl: '',
|
||||
modifyUrl: '',
|
||||
};
|
||||
9
app/admin/view/system/timer_log/index.html
Normal file
9
app/admin/view/system/timer_log/index.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="layuimini-container">
|
||||
<div class="layuimini-main">
|
||||
<table id="currentTable" class="layui-table layui-hide"
|
||||
data-auth-index="{:auth('system.timer_log/index')}"
|
||||
data-auth-read="{:auth('system.timer_log/read')}"
|
||||
lay-filter="currentTable">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
99
app/admin/view/system/timer_log/index.js
Normal file
99
app/admin/view/system/timer_log/index.js
Normal file
@@ -0,0 +1,99 @@
|
||||
$(function(){
|
||||
// 状态徽章模板
|
||||
var statusTemplet = function(d) {
|
||||
var statusMap = {
|
||||
'success': {text: '成功', color: '#5FB878', bgColor: '#e8f8ef'},
|
||||
'error': {text: '失败', color: '#FF5722', bgColor: '#ffe8e2'},
|
||||
'running': {text: '运行中', color: '#1E9FFF', bgColor: '#e2f1ff'},
|
||||
};
|
||||
var item = statusMap[d.status] || {text: d.status, color: '#999', bgColor: '#f5f5f5'};
|
||||
return '<span style="display:inline-block;padding:2px 10px;border-radius:3px;color:'+item.color+';background:'+item.bgColor+';font-size:12px;">'+item.text+'</span>';
|
||||
};
|
||||
|
||||
// 时间格式化模板
|
||||
var timeTemplet = function(d) {
|
||||
if (!d.start_time || d.start_time === 0) return '<span style="color:#999">-</span>';
|
||||
var date = new Date(d.start_time * 1000);
|
||||
var Y = date.getFullYear();
|
||||
var M = (date.getMonth()+1).toString().padStart(2,'0');
|
||||
var D = date.getDate().toString().padStart(2,'0');
|
||||
var h = date.getHours().toString().padStart(2,'0');
|
||||
var m = date.getMinutes().toString().padStart(2,'0');
|
||||
var s = date.getSeconds().toString().padStart(2,'0');
|
||||
return Y+'-'+M+'-'+D+' '+h+':'+m+':'+s;
|
||||
};
|
||||
|
||||
var endTimeTemplet = function(d) {
|
||||
if (!d.end_time || d.end_time === 0) return '<span style="color:#999">-</span>';
|
||||
var date = new Date(d.end_time * 1000);
|
||||
var Y = date.getFullYear();
|
||||
var M = (date.getMonth()+1).toString().padStart(2,'0');
|
||||
var D = date.getDate().toString().padStart(2,'0');
|
||||
var h = date.getHours().toString().padStart(2,'0');
|
||||
var m = date.getMinutes().toString().padStart(2,'0');
|
||||
var s = date.getSeconds().toString().padStart(2,'0');
|
||||
return Y+'-'+M+'-'+D+' '+h+':'+m+':'+s;
|
||||
};
|
||||
|
||||
// 耗时格式化模板
|
||||
var durationTemplet = function(d) {
|
||||
if (!d.duration || d.duration === 0) return '<span style="color:#999">-</span>';
|
||||
if (d.duration < 1000) {
|
||||
return d.duration + ' ms';
|
||||
}
|
||||
return (d.duration / 1000).toFixed(2) + ' s';
|
||||
};
|
||||
|
||||
// 错误信息截断模板
|
||||
var errorMsgTemplet = function(d) {
|
||||
if (!d.error_message) return '<span style="color:#999">-</span>';
|
||||
var text = d.error_message;
|
||||
if (text.length > 50) {
|
||||
return '<span title="'+text.replace(/"/g,'"').replace(/'/g,''')+'" style="cursor:pointer;" lay-event="showError">'+text.substring(0,50)+'...</span>';
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
ua.table.render({
|
||||
init: init,
|
||||
cols: [[
|
||||
{type: 'checkbox'},
|
||||
{field: 'id', title: 'ID', width: 80, sort: true},
|
||||
{field: 'task_name', title: '任务名称', width: 160, search: 'select', searchUrl: 'system.timer_log/index?selectFields=task_name'},
|
||||
{field: 'node_id', title: '节点ID', width: 120, search: 'select', searchUrl: 'system.timer_log/index?selectFields=node_id'},
|
||||
{field: 'run_type', title: '运行类型', width: 100},
|
||||
{field: 'start_time', title: '开始时间', width: 170, templet: timeTemplet, search: 'range'},
|
||||
{field: 'end_time', title: '结束时间', width: 170, templet: endTimeTemplet},
|
||||
{field: 'duration', title: '耗时', width: 100, templet: durationTemplet},
|
||||
{field: 'status', title: '状态', width: 100, templet: statusTemplet, search: 'select', selectList: {success:'成功', error:'失败', running:'运行中'}},
|
||||
{field: 'error_message', title: '错误信息', minWidth: 200, templet: errorMsgTemplet},
|
||||
{
|
||||
width: 100, title: '操作', templet: ua.table.tool, fixed: 'right', operat: [
|
||||
[{
|
||||
class: 'layui-btn layui-btn-primary layui-btn-xs',
|
||||
method: 'tab',
|
||||
field: 'id',
|
||||
text: '详情',
|
||||
title: '查看详情',
|
||||
auth: 'read',
|
||||
url: init.readUrl,
|
||||
icon: ''
|
||||
}]
|
||||
]
|
||||
},
|
||||
]],
|
||||
});
|
||||
|
||||
// 点击错误信息查看完整内容
|
||||
ua.table.on('tool(currentTable)', function(obj) {
|
||||
if (obj.event === 'showError') {
|
||||
var text = obj.data.error_message || '';
|
||||
layer.alert('<pre style="white-space:pre-wrap;word-break:break-all;max-height:400px;overflow-y:auto;">'+text.replace(/</g,'<').replace(/>/g,'>')+'</pre>', {
|
||||
title: '错误信息详情',
|
||||
area: ['600px', '400px']
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ua.listen();
|
||||
})
|
||||
139
app/admin/view/system/timer_log/read.html
Normal file
139
app/admin/view/system/timer_log/read.html
Normal file
@@ -0,0 +1,139 @@
|
||||
<div class="layuimini-container detail-container">
|
||||
<div class="layuimini-main">
|
||||
<div class="layui-card detail-card">
|
||||
<div class="layui-card-header detail-header">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-md9">
|
||||
<h2 class="detail-title">#{$row.id} {$title}</h2>
|
||||
<div class="detail-id">ID: {$row.id}</div>
|
||||
</div>
|
||||
<div class="layui-col-md3 text-right detail-actions">
|
||||
<button class="layui-btn layui-btn-primary" layuimini-content-href="{$Request.param.backTagId}" data-back="1">返回</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-card-body detail-content">
|
||||
<div class="layui-row layui-col-space12">
|
||||
<!-- 左侧主体内容 -->
|
||||
<div class="layui-col-md8 detail-main">
|
||||
<div class="detail-field-group">
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">任务名称</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.task_name"}
|
||||
{$row.task_name}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">执行节点ID</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.node_id"}
|
||||
{$row.node_id}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">运行类型</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.run_type"}
|
||||
{$row.run_type}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">开始时间</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.start_time"}
|
||||
{:date('Y-m-d H:i:s', $row['start_time'])}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">结束时间</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.end_time"}
|
||||
{:date('Y-m-d H:i:s', $row['end_time'])}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">耗时</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.duration"}
|
||||
{:php echo ($row['duration'] < 1000) ? $row['duration'].' ms' : round($row['duration']/1000, 2).' s';}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">错误信息</div>
|
||||
<div class="detail-field-value" style="white-space: pre-wrap;">
|
||||
{notempty name="row.error_message"}
|
||||
{$row.error_message|raw}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无内容</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">并发分片ID</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.concurrency_id"}
|
||||
{$row.concurrency_id}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧基础信息 -->
|
||||
<div class="layui-col-md4 detail-side">
|
||||
<h3 class="detail-side-title">基础信息</h3>
|
||||
<div class="detail-field-group">
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">ID</div>
|
||||
<div class="detail-field-value">{$row.id}</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">状态</div>
|
||||
<div class="detail-field-value">
|
||||
{switch name="row.status"}
|
||||
{case value="success"}<span style="display:inline-block;padding:2px 10px;border-radius:3px;color:#5FB878;background:#e8f8ef;font-size:12px;">成功</span>{/case}
|
||||
{case value="error"}<span style="display:inline-block;padding:2px 10px;border-radius:3px;color:#FF5722;background:#ffe8e2;font-size:12px;">失败</span>{/case}
|
||||
{case value="running"}<span style="display:inline-block;padding:2px 10px;border-radius:3px;color:#1E9FFF;background:#e2f1ff;font-size:12px;">运行中</span>{/case}
|
||||
{default /}<span style="color:#999">{$row.status}</span>
|
||||
{/switch}
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">创建时间</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.create_time"}
|
||||
{$row.create_time|date="Y-m-d H:i:s"}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
3
app/admin/view/system/timer_log/read.js
Normal file
3
app/admin/view/system/timer_log/read.js
Normal file
@@ -0,0 +1,3 @@
|
||||
$(function(){
|
||||
ua.listen();
|
||||
})
|
||||
@@ -20,5 +20,26 @@ class HostBase extends AdminController
|
||||
$this->model = new \app\admin\model\SystemHost();
|
||||
|
||||
$this->assign('select_list_status', $this->model::SELECT_LIST_STATUS, true);
|
||||
$this->assign('select_list_is_master', $this->model::SELECT_LIST_IS_MASTER, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主节点.
|
||||
*
|
||||
* @auth true
|
||||
*/
|
||||
public function setMaster()
|
||||
{
|
||||
$nodeId = $this->request->param('node_id', '');
|
||||
if (empty($nodeId)) {
|
||||
$this->error('参数错误');
|
||||
}
|
||||
|
||||
$result = \app\common\service\HostService::setMasterNode($nodeId);
|
||||
if ($result) {
|
||||
$this->success('主节点切换成功');
|
||||
} else {
|
||||
$this->error('节点不存在');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +26,6 @@ class SystemHostBase extends TimeModel
|
||||
protected $deleteTime = false;
|
||||
|
||||
public const SELECT_LIST_STATUS = ['0' => '离线', '1' => '在线'];
|
||||
|
||||
public const SELECT_LIST_IS_MASTER = ['0' => '从节点', '1' => '主节点'];
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ var init = {
|
||||
deleteUrl: 'system.host/delete',
|
||||
exportUrl: 'system.host/export',
|
||||
modifyUrl: 'system.host/modify',
|
||||
set_master_url: 'system.host/setMaster',
|
||||
};
|
||||
@@ -7,6 +7,7 @@
|
||||
data-auth-delete="{:auth('system.host/delete')}"
|
||||
data-auth-export="{:auth('system.host/export')}"
|
||||
data-auth-modify="{:auth('system.host/modify')}"
|
||||
data-auth-set-master="{:auth('system.host/setMaster')}"
|
||||
lay-filter="currentTable">
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,44 @@ $(function(){
|
||||
ua.table.render({
|
||||
init: init,
|
||||
cols: [[
|
||||
{type: 'checkbox'},
|
||||
{type: 'checkbox'},
|
||||
{field: 'id', title: 'ID'},
|
||||
{field: 'node_id', title: '节点ID'},
|
||||
{field: 'ip_address', title: 'IP地址'},
|
||||
{field: 'status', search: 'select', selectList: ua.getDataBrage('select_list_status'), title: '状态', templet: ua.table.switch},
|
||||
{field: 'is_master', search: 'select', selectList: ua.getDataBrage('select_list_is_master'), title: '节点角色', templet: function(data) {
|
||||
if (data.is_master == 1) {
|
||||
return '<span class="layui-badge layui-bg-green">主节点</span>';
|
||||
}
|
||||
return '<span class="layui-badge layui-bg-gray">从节点</span>';
|
||||
}},
|
||||
{field: 'last_heartbeat_at', title: '最后心跳时间'},
|
||||
{field: 'os_info', title: '系统信息'},
|
||||
{field: 'php_version', title: 'PHP版本'},
|
||||
{field: 'cpu_load', title: 'CPU负载'},
|
||||
{field: 'memory_usage', title: '内存占用'},
|
||||
{field: 'disk_free', title: '磁盘可用空间'},
|
||||
{field: 'disk_total', title: '磁盘总空间'},
|
||||
{field: 'create_time', title: '首次运行时间'},
|
||||
{width: 300, title: '操作', templet: ua.table.tool, operat: [
|
||||
'edit',
|
||||
[{
|
||||
text: '设为主节点',
|
||||
url: init.set_master_url,
|
||||
method: 'request',
|
||||
field: function(data) {
|
||||
return {node_id: data.node_id};
|
||||
},
|
||||
auth: 'set-master',
|
||||
class: 'layui-btn layui-btn-xs layui-btn-warm',
|
||||
title: '确认将该节点设为主节点?切换后原主节点将变为从节点。',
|
||||
_if: function(data) {
|
||||
return data.status == 1 && data.is_master != 1;
|
||||
}
|
||||
}],
|
||||
'delete'
|
||||
], fixed:'right'},
|
||||
|
||||
]],
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user