logtail.html 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
  6. <meta http-equiv="Cache-Control" content="no-siteapp;no-transform">
  7. <meta name="applicable-device" content="pc,mobile">
  8. <meta name="viewport" content="width=device-width, initial-scale=1">
  9. <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
  10. <title>tvbox实时日志</title>
  11. <script src="/static/js/jquery.min.js"></script>
  12. <script src="/static/js/grey.js"></script>
  13. <link href="/static/css/jquery-confirm.min.css" rel="stylesheet">
  14. <script src="/static/js/jquery-confirm.min.js"></script>
  15. <!-- <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>-->
  16. <!-- <link href="https://cdn.bootcdn.net/ajax/libs/jquery-confirm/3.3.4/jquery-confirm.min.css" rel="stylesheet">-->
  17. <!-- <script src="https://cdn.bootcdn.net/ajax/libs/jquery-confirm/3.3.4/jquery-confirm.min.js"></script>-->
  18. <style>
  19. .support{
  20. background: #5dc2f1;
  21. color: #FFFFFF;
  22. text-align: center;
  23. }
  24. #title{
  25. background: #000000;
  26. color: #5dc2f1;
  27. padding:3px 1px 3px 1px;
  28. }
  29. #height{
  30. margin-left: 10px;
  31. }
  32. #msg{
  33. /*white-space: pre;*/
  34. text-align: left;
  35. /*word-wrap: break-word;*/
  36. /*word-break: normal;*/
  37. }
  38. #log-container{
  39. overflow-y: scroll;
  40. background: #333;
  41. color: #aaa;
  42. /*上右下左*/
  43. padding:1px 2px 10px 2px;
  44. }
  45. .btn{
  46. background: #f06e57;
  47. color: #FFFFFF;
  48. margin: 0 5px 0 0;
  49. border: 2px solid #aaaaaa;
  50. border-radius: 2px;
  51. }
  52. .input{
  53. width: 70%;
  54. height: 20px;
  55. }
  56. #inputMsg{
  57. background: #333;
  58. padding:5px 1px 5px 1px;
  59. }
  60. #inputMsg span{
  61. color:#5dc2f1 ;
  62. }
  63. </style>
  64. </head>
  65. <body>
  66. <div class="support"></div>
  67. <div id="title">
  68. <strong>tvbox实时日志 by道长</strong>
  69. <span id="height"></span>
  70. <button id="clearLog" class="btn">清空日志</button>
  71. <button id="showInput" class="btn">显示输入框</button>
  72. <button id="clearInput" class="btn">清空输入框</button>
  73. <button id="autoClearInput" class="btn">自动清空</button>
  74. </div>
  75. <div id="inputMsg">
  76. <span>输入</span>
  77. <input type="text" class="input">
  78. <button id="sendMsg" class="btn">发送</button>
  79. </div>
  80. <div id="log-container">
  81. <div id="msg">
  82. </div>
  83. </div>
  84. <div class="support"></div>
  85. <script>
  86. Date.prototype.Format = function (fmt) { // author: meizz
  87. var o = {
  88. "M+": this.getMonth() + 1, // 月份
  89. "d+": this.getDate(), // 日
  90. "h+": this.getHours(), // 小时
  91. "m+": this.getMinutes(), // 分
  92. "s+": this.getSeconds(), // 秒
  93. "q+": Math.floor((this.getMonth() + 3) / 3), // 季度
  94. "S": this.getMilliseconds() // 毫秒
  95. };
  96. if (/(y+)/.test(fmt))
  97. fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
  98. for (var k in o)
  99. if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
  100. return fmt;
  101. }
  102. function getWsUrl() {
  103. let host = location.host;
  104. let hostname = location.hostname;
  105. let protocol = 'ws:';
  106. let port = location.port;
  107. let pathname = '/ws';
  108. let ws_port = parseInt(port)+1;
  109. return protocol+'//'+host+pathname;
  110. // return protocol+'//'+hostname+':'+ws_port+pathname;
  111. }
  112. // const websocketUrl = 'ws://localhost:8080/log';
  113. const websocketUrl = getWsUrl();
  114. const test_msg = true;
  115. const test_count = 10;
  116. const reconnect_time = 5000;
  117. var lockReconnect = false;//避免重复连接
  118. var ws = null; //WebSocket的引用
  119. var showInput;
  120. var autoClearInput;
  121. showInput = !!(localStorage.showInput && localStorage.showInput === '1');
  122. autoClearInput = !!(localStorage.autoClearInput && localStorage.autoClearInput === '1');
  123. var btn_showInput = $('#showInput');
  124. var btn_autoClearInput = $('#autoClearInput');
  125. if(!showInput){
  126. $('#inputMsg').hide();
  127. $('#clearInput').hide();
  128. $('#autoClearInput').hide();
  129. btn_showInput.text('显示输入框');
  130. btn_showInput[0].style.borderStyle = 'outset';
  131. }else{
  132. $('#inputMsg').show();
  133. $('#clearInput').show();
  134. $('#autoClearInput').show();
  135. btn_showInput.text('隐藏输入框');
  136. btn_showInput[0].style.borderStyle = 'inset';
  137. }
  138. if(autoClearInput){
  139. btn_autoClearInput.text('自动清空');
  140. btn_autoClearInput[0].style.borderStyle = 'inset';
  141. }else{
  142. btn_autoClearInput.text('手动清空');
  143. btn_autoClearInput[0].style.borderStyle = 'outset';
  144. }
  145. function initHeight(){//动态刷新日志框高度自适应设备
  146. var div_height = window.screen.availHeight;
  147. var height = Math.ceil(div_height*0.75);
  148. if(showInput){
  149. height-=35;
  150. }
  151. $('#height').text('日志窗口高度:'+height);
  152. $("#log-container").height(height);
  153. }
  154. initHeight();
  155. window.onresize = () =>{
  156.    //只要窗口高度发生变化,就会进入这里面,在这里就可以写,回到聊天最底部的逻辑
  157. initHeight();
  158. }
  159. function checkLoading(){// loading用,无实际意义
  160. $.confirm({
  161. closeIcon: true,
  162. title: '请稍等',
  163. content: '正在检查websocket连接...',
  164. autoClose: 'ok|2000',
  165. buttons: {
  166. ok: {
  167. text: '确定',
  168. action: function () {
  169. console.log('检查完毕,一切正常');
  170. }
  171. },
  172. cancel: {
  173. text: '取消',
  174. action(){
  175. // $.alert('已取消');
  176. }
  177. }
  178. }
  179. });
  180. }
  181. function checkSupport() {//检查浏览器是否支持websocket
  182. var sp = $(".support");
  183. if (window.WebSocket) {
  184. sp.html('您的浏览器支持多个websocket通信的实例');
  185. return true;
  186. }
  187. else {
  188. sp.html('您的浏览器不支持多个websocket通信的实例,建议使用火狐浏览器或者谷歌浏览器');
  189. return false;
  190. }
  191. }
  192. function createWebSocket(){//创建ws连接并监听ws事件
  193. let can_ws = checkSupport();
  194. // console.log('can_ws:',can_ws);
  195. if(can_ws){
  196. // 指定websocket路径
  197. try {
  198. console.log('开始连接:'+websocketUrl);
  199. ws = new WebSocket(websocketUrl);
  200. initEventHandle();
  201. }catch (e) {
  202. ws = null;
  203. reconnect();
  204. }
  205. }
  206. }
  207. /**
  208. * 自动重连
  209. */
  210. function reconnect() {
  211. if(!lockReconnect){
  212. lockReconnect = true;
  213. //没连接上会一直重连,设置延迟避免请求过多
  214. setTimeout(function () {
  215. createWebSocket(websocketUrl);
  216. addMsg("正在重连,当前时间"+new Date().Format("yyyy-MM-dd hh:mm:ss"));
  217. lockReconnect = false;
  218. }, reconnect_time); //这里设置重连间隔(ms)
  219. }
  220. }
  221. function initEventHandle(){
  222. ws.onopen = function(event) {
  223. addMsg("websocket连接成功,当前时间"+new Date().Format("yyyy-MM-dd hh:mm:ss"));
  224. }
  225. ws.onmessage = function(event) {
  226. // 接收服务端的实时日志并添加到HTML页面中
  227. let msg = event.data;
  228. addMsg(msg);
  229. };
  230. ws.onclose = function(event) {
  231. addMsg('websocket连接关闭');
  232. ws = null;
  233. reconnect();
  234. };
  235. ws.onerror = function(event){
  236. //如果出现连接、处理、接收、发送数据失败的时候触发onerror事件
  237. // let msg = "websocket发生错误:"+event.data;
  238. let msg = "websocket发生错误,连接状态码:"+ws.readyState;
  239. // console.log(event);
  240. console.log(msg);
  241. addMsg(msg);
  242. ws = null;
  243. reconnect();
  244. }
  245. }
  246. createWebSocket();
  247. function addMsg(msg){
  248. // 将Msg添加到日志框里
  249. if(msg&&msg.trim()&&!msg.endsWith('\n')){
  250. msg+='\n';
  251. }
  252. msg = msg.replaceAll('\n','</br>');
  253. $("#log-container div").append(msg);
  254. // 滚动条滚动到最低部
  255. $("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());
  256. }
  257. $(document).ready(function() {
  258. checkLoading();
  259. addMsg('websocket初始化中,当前ws服务地址=> '+websocketUrl);
  260. if(test_msg){
  261. for(let i=0;i<test_count;i++){
  262. addMsg('2022-11-15 10:12:50 - E:\\python\\mypython\\dr_py\\lib\\site-packages\\gevent\\pywsgi.py[line:1226]:INFO:dr.log -- 127.0.0.1 - - [2022-11-15 10:12:50] "GET /static/img/favicon.svg HTTP/1.1" 200 155239 0.001139\n');
  263. }
  264. }
  265. $('#clearLog').click(function () {
  266. $.confirm({
  267. title: '确认',
  268. content: '确认清空日志?',
  269. type: 'green',
  270. icon: 'glyphicon glyphicon-question-sign',
  271. buttons: {
  272. ok: {
  273. text: '确认',
  274. btnClass: 'btn-primary',
  275. action: function() {
  276. $("#log-container div").text('');
  277. }
  278. },
  279. cancel: {
  280. text: '取消',
  281. btnClass: 'btn-primary'
  282. }
  283. }
  284. });
  285. });
  286. $('#clearInput').click(function (){
  287. $('.input').val('');
  288. });
  289. $('#showInput').click(function (){
  290. // console.log(localStorage.showInput);
  291. if(!showInput){
  292. showInput = true;
  293. localStorage.showInput = '1';
  294. $('#inputMsg').show();
  295. $('#clearInput').show();
  296. $('#autoClearInput').show();
  297. btn_showInput.text('隐藏输入框');
  298. btn_showInput[0].style.borderStyle = 'inset';
  299. }else{
  300. showInput = false;
  301. localStorage.showInput = '0';
  302. $('#inputMsg').hide();
  303. $('#clearInput').hide();
  304. $('#autoClearInput').hide();
  305. btn_showInput.text('显示输入框');
  306. btn_showInput[0].style.borderStyle = 'outset';
  307. }
  308. initHeight();
  309. });
  310. $('#autoClearInput').click(function (){
  311. if(!autoClearInput){
  312. autoClearInput = true;
  313. localStorage.autoClearInput = '1';
  314. btn_autoClearInput.text('自动清空');
  315. btn_autoClearInput[0].style.borderStyle = 'inset';
  316. }else{
  317. autoClearInput = false;
  318. localStorage.autoClearInput = '0';
  319. btn_autoClearInput.text('手动清空');
  320. btn_autoClearInput[0].style.borderStyle = 'outset';
  321. }
  322. });
  323. $('#sendMsg').click(function (){
  324. let text_input = $('.input');
  325. let msg = text_input.val();
  326. if(msg){
  327. if(ws){
  328. addMsg('主动发送文本消息:'+msg);
  329. ws.send(msg);
  330. if(autoClearInput){
  331. text_input.val('');
  332. }
  333. }else{
  334. addMsg('ws未正常连接,待发送消息无效:'+msg);
  335. }
  336. }
  337. });
  338. });
  339. </script>
  340. </body>
  341. </html>