mesClientVersionList.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <% layout('/layouts/default.html', {title: '通用客户端版本管理', libs: ['dataGrid']}){ %>
  2. <div class="main-content"><div class="box box-main">
  3. <div class="box-header"><div class="box-title"><i class="fa icon-notebook"></i> 通用客户端版本管理</div><div class="box-tools pull-right">
  4. <% if(hasPermi('mes:clientVersion:edit')){ %><a href="${ctx}/mes/clientVersion/form" class="btn btn-default btnTool"><i class="fa fa-plus"></i> 新增版本</a><% } %>
  5. <a href="#" class="btn btn-default" id="btnSetting"><i class="fa fa-navicon"></i></a></div></div>
  6. <div class="box-body">
  7. <ul class="nav nav-tabs" role="tablist">
  8. <li class="active"><a href="#versionPanel" data-toggle="tab">版本发布</a></li>
  9. <li><a href="#runtimePanel" data-toggle="tab">客户端监控</a></li>
  10. </ul>
  11. <div class="tab-content" style="padding-top:15px">
  12. <div id="versionPanel" class="tab-pane active">
  13. <#form:form id="searchForm" model="${mesClientVersion}" action="${ctx}/mes/clientVersion/listData" method="post" class="form-inline hide" data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
  14. <#form:input path="ver" class="form-control width-120" placeholder="版本号"/>
  15. <#form:input path="content" class="form-control width-180" placeholder="更新内容"/>
  16. <select name="clientType" class="form-control"><option value="">全部客户端</option><option value="MANUAL" ${mesClientVersion.clientType == 'MANUAL' ? 'selected' : ''}>手工位通用</option><option value="QIMI" ${mesClientVersion.clientType == 'QIMI' ? 'selected' : ''}>气密通用</option><option value="RIVETING" ${mesClientVersion.clientType == 'RIVETING' ? 'selected' : ''}>拉铆通用</option></select>
  17. <#form:select path="state" dictType="mes_app_status" blankOption="true" class="form-control"/>
  18. <button type="submit" class="btn btn-primary btn-sm">查询</button>
  19. </#form:form>
  20. <table id="dataGrid"></table><div id="dataGridPage"></div>
  21. </div>
  22. <div id="runtimePanel" class="tab-pane">
  23. <div style="margin-bottom:8px">
  24. <button type="button" class="btn btn-sm btn-primary" onclick="$('#runtimeGrid').trigger('reloadGrid')"><i class="fa fa-refresh"></i> 刷新</button>
  25. <button type="button" class="btn btn-sm btn-default" id="btnShowOffline"><i class="fa fa-eye"></i> 显示离线</button>
  26. <span style="color:#888;margin-left:8px;font-size:12px">列表每次切到本页也会自动刷新</span>
  27. </div>
  28. <table id="runtimeGrid"></table><div id="runtimeGridPage"></div>
  29. </div>
  30. </div></div></div></div>
  31. <% } %>
  32. <style>
  33. #dataGrid td,
  34. #runtimeGrid td {
  35. text-align: center;
  36. }
  37. </style>
  38. <script>
  39. var CLIENT_TYPE_NAME = {MANUAL:'手工位通用', QIMI:'气密通用', RIVETING:'拉铆通用'};
  40. function clientTypeName(v){ return CLIENT_TYPE_NAME[v] || v || '-'; }
  41. // —— 版本发布 grid ——
  42. $('#dataGrid').dataGrid({
  43. searchForm: $('#searchForm'),
  44. columnModel: [
  45. {header:'客户端类型', name:'clientType', width:120, formatter:function(v){return clientTypeName(v);}},
  46. {header:'版本号', name:'ver', width:90, formatter:function(v,o,row){return '<a href="${ctx}/mes/clientVersion/form?id='+row.id+'" class="btnList">'+(v||row.id)+'</a>';}},
  47. {header:'更新内容', name:'content', width:250},
  48. {header:'状态', name:'state', width:80, formatter:function(v){return js.getDictLabel(${@DictUtils.getDictListJson('mes_app_status')},v,'未知',true);}},
  49. {header:'更新时间', name:'updateDate', width:150},
  50. {header:'操作', name:'actions', width:100, formatter:function(v,o,row){return '<a href="${ctx}/mes/clientVersion/form?id='+row.id+'" class="btnList"><i class="fa fa-pencil"></i> 编辑</a>';}}
  51. ]
  52. });
  53. // 表单页保存后通过 localStorage 通知列表刷新
  54. window.addEventListener('storage', function(e){
  55. if(e.key === 'mesClientVersion.reload') $('#dataGrid').trigger('reloadGrid');
  56. });
  57. // —— 客户端监控 grid ——
  58. var showOffline = false;
  59. function pad2(n){return (n<10?'0':'')+n;}
  60. function heartbeatCell(v){
  61. if(!v) return '<span class="label label-default">未知</span>';
  62. var t = new Date(String(v).replace(' ','T')).getTime();
  63. if(isNaN(t)) return '<span class="label label-default">'+v+'</span>';
  64. var gap = (Date.now()-t)/1000, lab;
  65. if(gap<60) lab='<span class="label label-success">在线</span>';
  66. else if(gap<180) lab='<span class="label label-warning">疑似离线</span>';
  67. else lab='<span class="label label-default">离线</span>';
  68. var d = new Date(t);
  69. var short = pad2(d.getMonth()+1)+'-'+pad2(d.getDate())+' '+pad2(d.getHours())+':'+pad2(d.getMinutes());
  70. return lab + ' <span style="color:#888">'+short+'</span>';
  71. }
  72. $('#runtimeGrid').dataGrid({
  73. url: '${ctx}/mes/clientRuntime/listData',
  74. postData: {showOffline: 'false'},
  75. // 与版本发布列表使用相同的分页配置,容器由 dataGrid 按表格 ID 自动匹配
  76. columnModel: [
  77. {header:'类型', name:'clientType', width:110, formatter:function(v){return clientTypeName(v);}},
  78. {header:'工位', name:'stationCode', width:100},
  79. {header:'工艺', name:'stationTitle', width:170, formatter:function(v){return v||'<span style="color:#bbb">-</span>';}},
  80. {header:'IP', name:'hostIp', width:140},
  81. {header:'JAR版本', name:'jarVersion', width:90},
  82. {header:'配置版本', name:'configVersion', width:90},
  83. {header:'心跳/在线', name:'lastHeartbeat', width:200, formatter:function(v){return heartbeatCell(v);}},
  84. {header:'操作', name:'actions', width:130, formatter:function(v,o,row){return '<a href="javascript:openRuntimeDialog(\''+row.id+'\')" class="btn btn-xs btn-primary"><i class="fa fa-cog"></i> 配置/升级</a>';}}
  85. ]
  86. });
  87. $('#btnShowOffline').on('click', function(){
  88. showOffline = !showOffline;
  89. $(this).html(showOffline ? '<i class="fa fa-eye-slash"></i> 隐藏离线' : '<i class="fa fa-eye"></i> 显示离线');
  90. $('#runtimeGrid').jqGrid('setGridParam', {postData: {showOffline: String(showOffline)}}).trigger('reloadGrid');
  91. });
  92. // —— 配置/升级弹窗 ——
  93. function openRuntimeDialog(id){
  94. $.getJSON('${ctx}/mes/clientRuntime/get', {id:id}, function(resp){
  95. if(!resp || resp.result !== 'true' || !resp.data){ top.layer.msg('未找到该客户端'); return; }
  96. renderRuntimeDialog(id, resp.data);
  97. });
  98. }
  99. function renderRuntimeDialog(id, row){
  100. // 优先展示客户端本地当前配置(心跳上报的),拿不到才用模板兜底
  101. var cfgStr = row.currentConfigJson || '';
  102. var pretty = '';
  103. var isTemplate = false;
  104. if(cfgStr){
  105. try{ pretty = JSON.stringify(JSON.parse(cfgStr), null, 2); }catch(e){ pretty = cfgStr; }
  106. } else {
  107. isTemplate = true;
  108. pretty = JSON.stringify({
  109. "mes.server_ip":"192.168.114.99",
  110. "mes.gw": row.stationCode || "OP130A",
  111. "mes.line_sn": row.lineSn || "XT",
  112. "mes.tcp_port":"3000",
  113. "mes.heart_beat_cycle":"60"
  114. }, null, 2);
  115. }
  116. var cfgHint = isTemplate
  117. ? '客户端尚未上报本地配置(首次心跳会带上)。这是一份模板,可以直接下发。'
  118. : '来自客户端最近一次心跳上报的本地 config.properties。修改后下发会覆盖同名 key(其他 key 保留),并触发重启。';
  119. var html = ''
  120. + '<div style="padding:18px 24px">'
  121. + '<div class="form-group"><label>客户端:</label> <b>'+clientTypeName(row.clientType)+' / '+(row.stationCode||'')+(row.stationTitle?'('+row.stationTitle+')':'')+' / '+(row.hostIp||'')+'</b>'
  122. + ' <span style="color:#888">(当前 JAR v'+(row.jarVersion||0)+',配置 v'+(row.configVersion||0)+')</span></div>'
  123. + '<hr style="margin:10px 0">'
  124. + '<div class="form-group"><label>目标 JAR 版本</label>'
  125. + '<select id="dlgJarVer" class="form-control"><option value="">— 不升级 —</option></select>'
  126. + '<small style="color:#888">下拉列出该类型下已启用的版本;改动后客户端将自动下载并重启</small>'
  127. + '</div>'
  128. + '<div class="form-group"><label>本地配置(留空 = 不下发配置)</label>'
  129. + '<textarea id="dlgCfgJson" class="form-control" rows="10" style="font-family:Consolas,monospace;font-size:12px">'+pretty.replace(/</g,'&lt;')+'</textarea>'
  130. + '<small style="color:#888">'+cfgHint+'</small>'
  131. + '</div>'
  132. + '</div>';
  133. top.layer.open({
  134. type: 1, title: '配置 / 升级客户端',
  135. area: ['620px', '520px'],
  136. content: html,
  137. btn: ['下发', '关闭'],
  138. success: function(layero, index){
  139. // 加载该 type 已启用的版本列表填下拉
  140. var $sel = $(layero).find('#dlgJarVer');
  141. $.post('${ctx}/mes/clientVersion/listData',
  142. {'clientType': row.clientType, 'state': '0', 'pageSize': 200, 'pageNo': 1},
  143. function(page){
  144. var list = (page && page.list) || [];
  145. list.forEach(function(v){
  146. var sel = (row.desiredJarVersion && String(v.ver) === String(row.desiredJarVersion)) ? ' selected' : '';
  147. $sel.append('<option value="'+v.ver+'"'+sel+'>v'+v.ver+' — '+(v.content||'').replace(/</g,'&lt;')+'</option>');
  148. });
  149. }, 'json');
  150. },
  151. yes: function(index, layero){
  152. var jar = $(layero).find('#dlgJarVer').val();
  153. var cfg = $.trim($(layero).find('#dlgCfgJson').val());
  154. var cv = null;
  155. if(cfg){
  156. try{ JSON.parse(cfg); }catch(e){ top.layer.msg('配置 JSON 格式错误'); return; }
  157. cv = (row.desiredConfigVersion || row.configVersion || 0) + 1;
  158. }
  159. $.post('${ctx}/mes/clientRuntime/command',
  160. {id:id, desiredJarVersion:jar||null, desiredConfigVersion:cv, desiredConfigJson:cfg||null},
  161. function(d){
  162. top.layer.msg(d.message||'已下发');
  163. top.layer.close(index);
  164. $('#runtimeGrid').trigger('reloadGrid');
  165. }, 'json');
  166. }
  167. });
  168. }
  169. // —— tab 切换:记录到 hash,刷新页面时恢复;每次切换刷新对应 grid ——
  170. function activateTab(h){
  171. var target = (h === '#runtime') ? '#runtimePanel' : '#versionPanel';
  172. $('a[data-toggle="tab"][href="'+target+'"]').tab('show');
  173. }
  174. $('a[data-toggle="tab"]').on('shown.bs.tab', function(e){
  175. var href = $(e.target).attr('href');
  176. location.hash = (href === '#runtimePanel') ? 'runtime' : 'version';
  177. if(href === '#runtimePanel') $('#runtimeGrid').trigger('reloadGrid');
  178. else $('#dataGrid').trigger('reloadGrid');
  179. });
  180. // 页面进入时恢复
  181. $(function(){ if(location.hash) activateTab(location.hash); });
  182. </script>