1import tornado
2from tornado.web import Application
3from tornado.web import RequestHandler
4from tornado.websocket import WebSocketHandler
5import os
6import json
7
8template = '''<!doctype html>
9<html lang="en">
10 <head>
11 <meta charset="UTF-8"/>
12 <title>Document</title>
13 <meta name="viewport" content="width=device-width, initial-scale=1">
14 <script src="https://cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>
15 <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
16 <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
17 <script type="text/javascript" charset="utf-8">
18 $(function() {
19 if (typeof String.prototype.startsWith != 'function') {
20 String.prototype.startsWith = function (prefix){
21 return this.slice(0, prefix.length) === prefix;
22 };
23 }
24 var output = $("#stdout-output").html();
25 function addToOutput(msg) {
26 /* output = $("#output").html() + '<br/>' + msg;*/
27 if (msg.startsWith('stderr:')) {
28 if (!$('a[href="#stderr"]').hasClass("alarm-report") && $('ul[role="tablist"] li.active a').attr('href') != '#stderr') {
29 $('a[href="#stderr"]').addClass("alarm-report")
30 }
31 output = $("#stderr-output").html() + msg.replace(/^stderr:/,'');
32 $("#stderr-output").html(output);
33 $('#stderr-output').scrollTop($('#stderr-output')[0].scrollHeight);
34 }else {
35 if (!$('a[href="#stdout"]').hasClass("alarm-report") && $('ul[role="tablist"] li.active a').attr('href') != '#stdout') {
36 $('a[href="#stdout"]').addClass("alarm-report")
37 }
38 output = $("#stdout-output").html() + msg.replace(/^stdout:/,'');
39 $("#stdout-output").html(output);
40 $('#stdout-output').scrollTop($('#stdout-output')[0].scrollHeight);
41 }
42 }
43 $('a#clear').click(function() {
44 var active_tab = $('ul[role="tablist"] li.active a').attr('href');
45 $(active_tab + '-output').html('');
46 })
47 $('a#load').click(function() {
48 var active_tab = $('ul[role="tablist"] li.active a').attr('href');
49 ws.send(JSON.stringify({room:'load',msg:active_tab.replace(/^#/,'')}));
50 })
51 $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
52 if ($(e.target).hasClass("alarm-report")) {
53 $(e.target).removeClass("alarm-report")
54 }
55 })
56 if ("MozWebSocket" in window) {
57 WebSocket = MozWebSocket;
58 }
59 if (WebSocket) {
60 var ws = new WebSocket("ws://%s/show");
61 ws.onopen = function() {};
62 ws.onmessage = function (evt) {
63 addToOutput(evt.data);
64 };
65 ws.onclose = function() {};
66 } else {
67 alert("WebSocket not supported");
68 }
69 })
70 </script>
71 <style type="text/css">
72 .input-group {
73 margin-bottom: 5px;
74 }
75 .input-group-addon {
76 background-color:#337ab7;
77 color:#fff;
78 border-color:#337ab7;
79 }
80 li[aria-selected="true"] {
81 display:none;
82 }
83 li[role="presentation"] a{
84 border-bottom-left-radius:0;
85 border-bottom-right-radius:0;
86 }
87 .well {
88 color:#eee;
89 border-top:0;
90 border-top-left-radius:0;
91 border-top-right-radius:0;
92 }
93 #stdout-output,#stderr-output {
94 background-color:#333;
95 height:600px;
96 overflow-y:auto;
97 padding:10px;
98 }
99 .alarm-report {
100 border:2px solid #333;
101 border-bottom:none;
102 animation: flash 1s linear infinite;
103 }
104
105 @keyframes flash{
106 from {
107 border-color: #333;
108 }
109 to {
110 border-color: red;
111 }
112 }
113 </style>
114 </head>
115 <body>
116 <div class="row">
117 <div class="col-md-offset-2 col-md-8">
118 <!-- Nav tabs -->
119 <ul class="nav nav-pills nav-justified" role="tablist">
120 <li role="presentation" class="active">
121 <a href="#stdout" aria-controls="stdout" role="tab" data-toggle="tab">标准输出</a>
122 </li>
123 <li role="presentation">
124 <a href="#stderr" aria-controls="stderr" role="tab" data-toggle="tab">错误输出</a>
125 </li>
126 <li role="presentation">
127 <a href="javascript:void(0);" id="load">载入历史</a>
128 </li>
129 <li role="presentation">
130 <a href="javascript:void(0);" id="clear">清空</a>
131 </li>
132 </ul>
133 <!-- Tab panes -->
134 <div class="tab-content">
135 <div role="tabpanel" class="tab-pane active" id="stdout">
136 <pre contentEditable="false" class="well" id="stdout-output"></pre>
137 </div>
138 <div role="tabpanel" class="tab-pane" id="stderr">
139 <pre contentEditable="false" class="well" id="stderr-output"></pre>
140 </div>
141 </div>
142 </div>
143 </div>
144 </body>
145</html>
146'''
147
148
149def create_app():
150 handlers = [(r"/", AnsibleHandler), (r'/show', CommandHandler)]
151 app = Application(
152 handlers=handlers, debug=True, cookie_secret='asdadasdadasdasdasda')
153 return app
154
155
156class cd(object):
157 def __init__(self, newPath):
158 self.newPath = os.path.expanduser(newPath)
159
160 def __enter__(self):
161 self.savedPath = os.getcwd()
162 os.chdir(self.newPath)
163
164 def __exit__(self, etype, value, traceback):
165 os.chdir(self.savedPath)
166
167
168class AnsibleHandler(RequestHandler):
169 def get(self):
170 self.write(template % (self.request.host))
171
172
173class CommandHandler(WebSocketHandler):
174 def open(self):
175 print("WebSocket opened")
176 LISTENERS.append(self)
177
178 def on_message(self, message):
179 message = json.loads(message)
180 # self.write_message(u"You said: " + message['msg'])
181 if message['room'] == 'load':
182 out_file = STDOUT_FILENAME if message[
183 'msg'] == 'stdout' else STDERR_FILENAME
184 if os.path.getsize(out_file) > 10240000:
185 self.write_message('file is too large!')
186 else:
187 with open(out_file, 'r') as f:
188 f.seek(0, os.SEEK_END)
189 fsize = f.tell()
190 f.seek(max(fsize - 1024, 0), 0)
191 for line in f.readlines()[-800:]:
192 self.write_message('{}:{}'.format(message['msg'],
193 line))
194
195 def on_close(self):
196 print("WebSocket closed")
197 try:
198 LISTENERS.remove(self)
199 except:
200 pass
201
202
203def tail_file():
204 where = stdout_file.tell()
205 line = stdout_file.readline()
206 if not line:
207 stdout_file.seek(where)
208 else:
209 for element in LISTENERS:
210 element.write_message('stdout:{}'.format(line))
211 where = stderr_file.tell()
212 line = stderr_file.readline()
213 if not line:
214 stderr_file.seek(where)
215 else:
216 for element in LISTENERS:
217 element.write_message('stderr:{}'.format(line))
218
219
220if __name__ == '__main__':
221 STDOUT_FILENAME = 'logs/tail.log'
222 STDERR_FILENAME = 'logs/tail_err.log'
223 STDOUT_FILENAME = os.path.abspath(STDOUT_FILENAME)
224 STDERR_FILENAME = os.path.abspath(STDERR_FILENAME)
225 LISTENERS = []
226 stdout_file = open(STDOUT_FILENAME)
227 stderr_file = open(STDERR_FILENAME)
228 stdout_file.seek(os.path.getsize(STDOUT_FILENAME))
229 stderr_file.seek(os.path.getsize(STDERR_FILENAME))
230
231 app = create_app()
232
233 http_server = tornado.httpserver.HTTPServer(app)
234 http_server.listen(8000, '0.0.0.0')
235
236 tailed_callback = tornado.ioloop.PeriodicCallback(tail_file, 5)
237 tailed_callback.start()
238
239 io_loop = tornado.ioloop.IOLoop.instance()
240 try:
241 io_loop.start()
242 except SystemExit as KeyboardInterrupt:
243 io_loop.stop()
244 stdout_file.close()
245 stderr_file.close()
作者:
honmaple
加载评论
知识共享署名-非商业性使用-相同方式共享4.0国际许可协议