mesClientVersionList.html 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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="javascript:openVersionForm()" 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. <table id="runtimeGrid"></table><div id="runtimeGridPage"></div>
  24. </div>
  25. </div></div></div></div>
  26. <% } %>
  27. <script>
  28. var CLIENT_TYPE_NAME = {MANUAL:'手工位通用', QIMI:'气密通用', RIVETING:'拉铆通用'};
  29. function clientTypeName(v){ return CLIENT_TYPE_NAME[v] || v || '-'; }
  30. // —— 版本发布 grid ——
  31. $('#dataGrid').dataGrid({
  32. searchForm: $('#searchForm'),
  33. columnModel: [
  34. {header:'客户端类型', name:'clientType', width:120, formatter:function(v){return clientTypeName(v);}},
  35. {header:'版本号', name:'ver', width:90, formatter:function(v,o,row){return '<a href="javascript:openVersionForm(\''+row.id+'\')" class="btnList">'+(v||row.id)+'</a>';}},
  36. {header:'更新内容', name:'content', width:250},
  37. {header:'状态', name:'state', width:80, formatter:function(v){return js.getDictLabel(${@DictUtils.getDictListJson('mes_app_status')},v,'未知',true);}},
  38. {header:'更新时间', name:'updateDate', width:150},
  39. {header:'操作', name:'actions', width:100, formatter:function(v,o,row){return '<a href="javascript:openVersionForm(\''+row.id+'\')" class="btnList"><i class="fa fa-pencil"></i> 编辑</a>';}}
  40. ]
  41. });
  42. // 用 layer 弹窗打开版本表单,保存后自动刷新 grid(替代原 tab 打开)
  43. function openVersionForm(id){
  44. var url = '${ctx}/mes/clientVersion/form' + (id ? '?id='+id : '');
  45. top.layer.open({
  46. type: 2, title: id ? '编辑版本' : '新增版本',
  47. area: ['680px', '560px'], content: url,
  48. end: function(){ $('#dataGrid').trigger('reloadGrid'); }
  49. });
  50. }
  51. // —— 客户端监控 grid ——
  52. function heartbeatCell(v){
  53. if(!v) return '<span class="label label-default">未知</span>';
  54. var t = new Date(v.replace(' ','T')).getTime();
  55. if(isNaN(t)) return '<span class="label label-default">'+v+'</span>';
  56. var gap = (Date.now()-t)/1000, lab;
  57. if(gap<60) lab='<span class="label label-success">在线</span>';
  58. else if(gap<180) lab='<span class="label label-warning">疑似离线</span>';
  59. else lab='<span class="label label-default">离线</span>';
  60. return lab + ' <span style="color:#888">'+v+'</span>';
  61. }
  62. $('#runtimeGrid').dataGrid({
  63. url: '${ctx}/mes/clientRuntime/listData',
  64. pager: '#runtimeGridPage',
  65. columnModel: [
  66. {header:'类型', name:'clientType', width:110, formatter:function(v){return clientTypeName(v);}},
  67. {header:'工位', name:'stationCode', width:100},
  68. {header:'工艺', name:'stationTitle', width:170, formatter:function(v){return v||'<span style="color:#bbb">-</span>';}},
  69. {header:'IP', name:'hostIp', width:140},
  70. {header:'JAR版本', name:'jarVersion', width:90},
  71. {header:'配置版本', name:'configVersion', width:90},
  72. {header:'心跳/在线', name:'lastHeartbeat', width:260, formatter:function(v){return heartbeatCell(v);}},
  73. {header:'目标JAR', name:'desiredJarVersion', width:90},
  74. {header:'目标配置', name:'desiredConfigVersion', width:90},
  75. {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>';}}
  76. ]
  77. });
  78. // —— 配置/升级弹窗 ——
  79. function openRuntimeDialog(id){
  80. $.getJSON('${ctx}/mes/clientRuntime/get', {id:id}, function(resp){
  81. if(!resp || resp.result !== 'true' || !resp.data){ top.layer.msg('未找到该客户端'); return; }
  82. renderRuntimeDialog(id, resp.data);
  83. });
  84. }
  85. function renderRuntimeDialog(id, row){
  86. // 优先展示客户端本地当前配置(心跳上报的),拿不到才用模板兜底
  87. var cfgStr = row.currentConfigJson || '';
  88. var pretty = '';
  89. var isTemplate = false;
  90. if(cfgStr){
  91. try{ pretty = JSON.stringify(JSON.parse(cfgStr), null, 2); }catch(e){ pretty = cfgStr; }
  92. } else {
  93. isTemplate = true;
  94. pretty = JSON.stringify({
  95. "mes.server_ip":"192.168.114.99",
  96. "mes.gw": row.stationCode || "OP130A",
  97. "mes.line_sn": row.lineSn || "XT",
  98. "mes.tcp_port":"3000",
  99. "mes.heart_beat_cycle":"60"
  100. }, null, 2);
  101. }
  102. var cfgHint = isTemplate
  103. ? '客户端尚未上报本地配置(首次心跳会带上)。这是一份模板,可以直接下发。'
  104. : '来自客户端最近一次心跳上报的本地 config.properties。修改后下发会覆盖同名 key(其他 key 保留),并触发重启。';
  105. var html = ''
  106. + '<div style="padding:18px 24px">'
  107. + '<div class="form-group"><label>客户端:</label> <b>'+clientTypeName(row.clientType)+' / '+(row.stationCode||'')+(row.stationTitle?'('+row.stationTitle+')':'')+' / '+(row.hostIp||'')+'</b>'
  108. + ' <span style="color:#888">(当前 JAR v'+(row.jarVersion||0)+',配置 v'+(row.configVersion||0)+')</span></div>'
  109. + '<hr style="margin:10px 0">'
  110. + '<div class="form-group"><label>目标 JAR 版本</label>'
  111. + '<select id="dlgJarVer" class="form-control"><option value="">— 不升级 —</option></select>'
  112. + '<small style="color:#888">下拉列出该类型下已启用的版本;改动后客户端将自动下载并重启</small>'
  113. + '</div>'
  114. + '<div class="form-group"><label>本地配置(留空 = 不下发配置)</label>'
  115. + '<textarea id="dlgCfgJson" class="form-control" rows="10" style="font-family:Consolas,monospace;font-size:12px">'+pretty.replace(/</g,'&lt;')+'</textarea>'
  116. + '<small style="color:#888">'+cfgHint+'</small>'
  117. + '</div>'
  118. + '</div>';
  119. top.layer.open({
  120. type: 1, title: '配置 / 升级客户端',
  121. area: ['620px', '520px'],
  122. content: html,
  123. btn: ['下发', '关闭'],
  124. success: function(layero, index){
  125. // 加载该 type 已启用的版本列表填下拉
  126. var $sel = $(layero).find('#dlgJarVer');
  127. $.post('${ctx}/mes/clientVersion/listData',
  128. {'clientType': row.clientType, 'state': '0', 'pageSize': 200, 'pageNo': 1},
  129. function(page){
  130. var list = (page && page.list) || [];
  131. list.forEach(function(v){
  132. var sel = (row.desiredJarVersion && String(v.ver) === String(row.desiredJarVersion)) ? ' selected' : '';
  133. $sel.append('<option value="'+v.ver+'"'+sel+'>v'+v.ver+' — '+(v.content||'').replace(/</g,'&lt;')+'</option>');
  134. });
  135. }, 'json');
  136. },
  137. yes: function(index, layero){
  138. var jar = $(layero).find('#dlgJarVer').val();
  139. var cfg = $.trim($(layero).find('#dlgCfgJson').val());
  140. var cv = null;
  141. if(cfg){
  142. try{ JSON.parse(cfg); }catch(e){ top.layer.msg('配置 JSON 格式错误'); return; }
  143. cv = (row.desiredConfigVersion || row.configVersion || 0) + 1;
  144. }
  145. $.post('${ctx}/mes/clientRuntime/command',
  146. {id:id, desiredJarVersion:jar||null, desiredConfigVersion:cv, desiredConfigJson:cfg||null},
  147. function(d){
  148. top.layer.msg(d.message||'已下发');
  149. top.layer.close(index);
  150. $('#runtimeGrid').trigger('reloadGrid');
  151. }, 'json');
  152. }
  153. });
  154. }
  155. // —— tab 切换:记录到 hash,刷新页面时恢复;每次切换刷新对应 grid ——
  156. function activateTab(h){
  157. var target = (h === '#runtime') ? '#runtimePanel' : '#versionPanel';
  158. $('a[data-toggle="tab"][href="'+target+'"]').tab('show');
  159. }
  160. $('a[data-toggle="tab"]').on('shown.bs.tab', function(e){
  161. var href = $(e.target).attr('href');
  162. location.hash = (href === '#runtimePanel') ? 'runtime' : 'version';
  163. if(href === '#runtimePanel') $('#runtimeGrid').trigger('reloadGrid');
  164. else $('#dataGrid').trigger('reloadGrid');
  165. });
  166. // 页面进入时恢复
  167. $(function(){ if(location.hash) activateTab(location.hash); });
  168. </script>