$(function(){
// ============ 节点叠加层状态(模块级) ============
// currentNodeId: 当前选中的节点 ID;为空表示纯配置视图(配置基础层)
// nodeMap: { node_id: {node_id, is_master, ip_address, last_heartbeat_at} }
// onlineNodeCount: 在线节点总数(用于 all 模式全网预估提示)
var currentNodeId = '';
var nodeMap = {};
var onlineNodeCount = 0;
var tableId = init.tableRenderId;
// ============ 归属可视化(D19 + run_type + 主节点判断)============
// 入参:run_type、status、当前选中节点是否主节点
// 返回:{text, class} 用于渲染徽章
function calcOwnership(runType, status, isMaster) {
if (status == 0) {
return { text: '已停用', cls: 'layui-badge layui-bg-gray' };
}
switch (runType) {
case 'all':
// 所有节点都跑(含当前节点)
return { text: '当前自动', cls: 'layui-badge layui-bg-green' };
case 'main':
if (isMaster == 1) {
return { text: '当前(主)自动', cls: 'layui-badge layui-bg-green' };
}
// 非主节点:此任务不会在当前节点调度
return { text: '仅主节点', cls: 'layui-badge layui-bg-orange' };
case 'auto':
// 多节点竞争执行(谁抢到谁跑)
return { text: '竞争', cls: 'layui-badge layui-bg-blue' };
case 'manual':
// 仅手动触发
return { text: '仅手动', cls: 'layui-badge layui-bg-orange' };
default:
return { text: runType || '-', cls: 'layui-badge' };
}
}
// ============ 加载节点列表(hostList 接口)============
function loadNodeList(callback) {
ua.request.get({
url: ua.url('system.timer_config/hostList'),
prefix: true,
}, function (res) {
var list = (res && res.data) ? res.data : [];
onlineNodeCount = list.length;
nodeMap = {};
var $sel = $('#node_filter');
// 保留首项(全局配置)
$sel.find('option').not(':first').remove();
$.each(list, function (i, item) {
nodeMap[item.node_id] = item;
var masterTag = item.is_master == 1 ? ' [主]' : '';
var ip = item.ip_address || '';
$sel.append('');
});
// 重新渲染 select
if (window.layui && layui.form) {
layui.form.render('select', 'timerNodeFilter');
}
if (typeof callback === 'function') {
callback(list);
}
}, function (res) {
// hostList 失败:保持纯配置视图(不阻塞页面)
$('#node_hint').attr('class', 'layui-badge').text('节点列表加载失败:' + (res && res.msg ? res.msg : '未知错误')).show();
});
}
// ============ 更新节点 hint 提示 ============
function updateNodeHint() {
var $hint = $('#node_hint');
if (!currentNodeId) {
$hint.hide();
return;
}
var node = nodeMap[currentNodeId];
if (!node) {
$hint.hide();
return;
}
var master = node.is_master == 1 ? '主节点' : '非主节点';
var ip = node.ip_address || '';
$hint.attr('class', 'layui-badge layui-bg-blue')
.text('当前视角:' + node.node_id + ' / ' + master + (ip ? ' / ' + ip : ''))
.show();
}
// ============ 表格 cols(基础配置层 + 节点叠加列始终在,按节点上下文渲染)============
var cols = [[
{ type: 'checkbox' },
{ field: 'id', title: 'ID', width: 80 },
{ field: 'task_name', title: '任务名称', minWidth: 180 },
{
field: 'run_type',
search: 'select',
selectList: ua.getDataBrage('select_list_run_type'),
title: '运行类型',
width: 110,
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: 90,
templet: ua.table.switch
},
// 并发数:NULL=继承代码默认;正整数=覆盖;行内可改(edit:'text' → modify)
{
field: 'concurrency',
title: '并发',
width: 110,
align: 'center',
search: false,
edit: 'text',
templet: function (d) {
if (d.concurrency === null || d.concurrency === undefined || d.concurrency === '') {
return '默认';
}
return d.concurrency;
}
},
// 手动触发目标节点(只读展示)
{
field: 'trigger_node_id',
title: '触发节点',
width: 140,
search: false,
templet: function (d) {
if (!d.trigger_node_id) {
return '-';
}
return d.trigger_node_id;
}
},
// 归属列(节点叠加):无选中节点时显示"全局"占位,有节点时按 run_type+主节点算
{
field: '_ownership',
title: '归属',
width: 130,
align: 'center',
search: false,
templet: function (d) {
if (!currentNodeId) {
return '全局';
}
var node = nodeMap[currentNodeId];
var isMaster = node ? node.is_master : 0;
var info = calcOwnership(d.run_type, d.status, isMaster);
return '' + info.text + '';
}
},
{
field: 'is_synced',
search: 'select',
selectList: ua.getDataBrage('select_list_is_synced'),
title: '同步状态',
width: 100,
templet: function (d) {
if (d.is_synced == 1) {
return '已同步';
}
return '未同步';
}
},
{ field: 'last_execute_node', title: '最后执行节点', width: 140, search: false },
{
field: 'last_execute_time',
title: '最后执行时间',
width: 170,
search: 'range',
templet: function (d) {
if (!d.last_execute_time || d.last_execute_time == 0) {
return '暂无';
}
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: 250, 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',
[{
// 手动触发:定向选中节点(method:none,自处理 click + POST body 带 trigger_node_id)
// D19:跨 run_type、穿透 status(调试工作流)
class: 'layui-btn layui-btn-warm layui-btn-xs timer-trigger-btn',
method: 'none',
field: 'id',
text: '触发',
title: '手动触发到选中节点',
auth: 'trigger',
url: init.triggerUrl,
icon: '',
extend: '',
// 节点叠加层启用:必须选中节点才显示触发按钮
_if: function () {
return currentNodeId !== '';
}
}]
]
},
]];
// ============ 初始化表格(配置基础层,永远渲染)============
ua.table.render({
init: init,
cols: cols,
});
ua.listen();
// ============ 行内编辑(concurrency)校验 + all 模式预估提示 ============
// ua.table 已通过 listenEdit 绑定 edit 事件并 POST 到 modifyUrl;
// 这里挂钩 layui table edit 事件做前置校验 + all 模式预估提示
if (window.layui && layui.table) {
layui.table.on('edit(' + tableId + '_LayFilter)', function (obj) {
if (obj.field === 'concurrency') {
var val = obj.value;
// 空:行内编辑不允许置空(继承默认请走 edit 表单),modify 接口也会拒绝
if (val === '' || !/^[1-9]\d*$/.test(val)) {
ua.msg.error('concurrency 必须是正整数(继承默认请走编辑表单)');
layui.table.reloadData(tableId);
return false;
}
// all 模式预估提示:全网并发 = 在线节点数 × 此值
var rowData = obj.data;
if (rowData.run_type === 'all' && onlineNodeCount > 0) {
var total = onlineNodeCount * parseInt(val, 10);
setTimeout(function () {
layer.tips(
'all 模式:全网预估 = ' + onlineNodeCount + ' 节点 × ' + val + ' = ' + total + ' 并发',
'.layui-table-edit-tips',
{ tips: [2, '#FF5722'], time: 6000 }
);
}, 100);
}
}
});
}
// ============ 手动触发按钮(定向选中节点)============
// method:'none' 不绑定默认行为,自处理 click
$(document).on('click', '.timer-trigger-btn', function (e) {
e.preventDefault();
var taskId = $(this).attr('data-id');
if (!taskId) {
ua.msg.error('未找到任务 ID');
return;
}
if (!currentNodeId) {
// 兜底(_if 已阻止渲染,但用户切节点后表格未 reload 时仍可能进入)
ua.msg.error('请先在顶部选择要触发的节点');
return;
}
var node = nodeMap[currentNodeId] || {};
var master = node.is_master == 1 ? '主节点' : '非主节点';
var ip = node.ip_address || '';
ua.msg.confirm(
'确定将此任务触发到节点 [' + currentNodeId + ' / ' + master + (ip ? ' / ' + ip : '') + '] 执行?\n(跨 run_type、穿透 status)',
function () {
ua.request.post({
url: ua.url(init.triggerUrl + '?id=' + taskId),
prefix: true,
data: { trigger_node_id: currentNodeId },
}, function (res) {
ua.msg.success(res.msg || '触发成功,等待定时器执行', function () {
layui.table.reloadData(tableId);
});
});
}
);
});
// ============ 节点选择器联动 ============
if (window.layui && layui.form) {
layui.form.on('select(node_filter)', function (data) {
currentNodeId = data.value || '';
updateNodeHint();
// 通知后端 reload(后端可选记录 node 上下文;归属列由前端 JS 算,不依赖后端)
layui.table.reloadData(tableId, {
where: { node_id: currentNodeId }
});
});
}
// ============ 首次加载节点列表(异步,不阻塞表格渲染)============
loadNodeList();
});