logtail.html 13 KB

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