Files
ulthon_admin/extend/base/admin/view/system/timer_config/index.js
augushong 009647f96f refactor(timer): TimerConfig 控制器与视图迁移到 Base 层
控制器逻辑移入 extend/base/admin/controller/system/TimerConfigBase.php,app 层改为空壳继承。9 个视图文件迁移到 extend/base/admin/view/system/timer_config/。index.js 补 toolbar: ['refresh', 'export'] 声明(C 类页面,去掉默认的 add/delete)。

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-07-25 22:39:20 +08:00

312 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

$(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('<option value="' + item.node_id + '">' + item.node_id + masterTag + ' (' + ip + ')</option>');
});
// 重新渲染 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 '<span class="layui-badge layui-bg-gray">默认</span>';
}
return d.concurrency;
}
},
// 手动触发目标节点(只读展示)
{
field: 'trigger_node_id',
title: '触发节点',
width: 140,
search: false,
templet: function (d) {
if (!d.trigger_node_id) {
return '<span class="layui-text-em">-</span>';
}
return d.trigger_node_id;
}
},
// 归属列(节点叠加):无选中节点时显示"全局"占位,有节点时按 run_type+主节点算
{
field: '_ownership',
title: '归属',
width: 130,
align: 'center',
search: false,
templet: function (d) {
if (!currentNodeId) {
return '<span class="layui-badge layui-bg-gray">全局</span>';
}
var node = nodeMap[currentNodeId];
var isMaster = node ? node.is_master : 0;
var info = calcOwnership(d.run_type, d.status, isMaster);
return '<span class="' + info.cls + '">' + info.text + '</span>';
}
},
{
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, 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 '<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: 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 !== '';
}
}]
]
},
]];
// ============ 初始化表格(配置基础层,永远渲染)============
// C 类页面有限操作add/delete 已在控制器禁用),仅保留 refresh + export
ua.table.render({
init: init,
toolbar: ['refresh', 'export'],
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();
});