Mercurial > templog
comparison web/templog.py @ 293:d15dda1b1f76
merge
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Sat, 06 Jul 2019 18:29:45 +0800 |
parents | f7261dd970da |
children | 6bacd8ca9f8f |
comparison
equal
deleted
inserted
replaced
292:28eb733cb803 | 293:d15dda1b1f76 |
---|---|
9 import urllib | 9 import urllib |
10 import sys | 10 import sys |
11 import os | 11 import os |
12 import traceback | 12 import traceback |
13 import fcntl | 13 import fcntl |
14 import hashlib | |
14 | 15 |
15 import bottle | 16 import bottle |
16 from bottle import route, request, response | 17 from bottle import route, request, response |
17 | 18 |
18 import config | 19 import config |
21 import atomicfile | 22 import atomicfile |
22 | 23 |
23 DATE_FORMAT = '%Y%m%d-%H.%M' | 24 DATE_FORMAT = '%Y%m%d-%H.%M' |
24 ZOOM_SCALE = 2.0 | 25 ZOOM_SCALE = 2.0 |
25 | 26 |
27 class TemplogBottle(bottle.Bottle): | |
28 def run(*args, **argm): | |
29 argm['server'] = 'gevent' | |
30 super(TemplogBottle, self).run(*args, **argm) | |
31 | |
32 bottle.default_app.push(TemplogBottle()) | |
33 | |
34 secure.setup_csrf() | |
35 | |
26 @route('/update', method='post') | 36 @route('/update', method='post') |
27 def update(): | 37 def update(): |
28 js_enc = request.forms.data | 38 js_enc = request.forms.data |
29 mac = request.forms.hmac | 39 mac = request.forms.hmac |
30 | 40 |
31 if hmac.new(config.HMAC_KEY, js_enc).hexdigest() != mac: | 41 h = hmac.new(config.HMAC_KEY, js_enc.strip(), hashlib.sha256).hexdigest() |
42 if h != mac: | |
32 raise bottle.HTTPError(code = 403, output = "Bad key") | 43 raise bottle.HTTPError(code = 403, output = "Bad key") |
33 | 44 |
34 js = zlib.decompress(binascii.a2b_base64(js_enc)) | 45 js = zlib.decompress(binascii.a2b_base64(js_enc)) |
35 | 46 |
36 params = json.loads(js) | 47 params = json.loads(js) |
37 | 48 |
38 log.parse(params) | 49 log.parse(params) |
39 | 50 |
40 return "OK" | 51 return "OK" |
52 | |
53 def make_graph(length, end): | |
54 length_minutes = int(length) | |
55 end = datetime.strptime(end, DATE_FORMAT) | |
56 start = end - timedelta(minutes=length_minutes) | |
57 | |
58 start_epoch = time.mktime(start.timetuple()) | |
59 return log.graph_png(start_epoch, length_minutes * 60) | |
60 | |
61 def encode_data(data, mimetype): | |
62 return 'data:%s;base64,%s' % (mimetype, binascii.b2a_base64(data).rstrip()) | |
41 | 63 |
42 @route('/graph.png') | 64 @route('/graph.png') |
43 def graph(): | 65 def graph(): |
44 length_minutes = int(request.query.length) | |
45 end = datetime.strptime(request.query.end, DATE_FORMAT) | |
46 start = end - timedelta(minutes=length_minutes) | |
47 | |
48 response.set_header('Content-Type', 'image/png') | 66 response.set_header('Content-Type', 'image/png') |
49 start_epoch = time.mktime(start.timetuple()) | 67 minutes, endstr = get_request_zoom() |
50 return log.graph_png(start_epoch, length_minutes * 60) | 68 return make_graph(minutes, endstr) |
51 | 69 |
52 @route('/set/update', method='post') | 70 @route('/set/update', method='post') |
53 def set_update(): | 71 def set_update(): |
72 if not secure.check_cookie(config.ALLOWED_USERS): | |
73 # the "Save" button should be disabled if the cert wasn't | |
74 # good | |
75 response.status = 403 | |
76 return "No cert, dodginess" | |
77 | |
54 post_json = json.loads(request.forms.data) | 78 post_json = json.loads(request.forms.data) |
55 | 79 |
56 csrf_blob = post_json['csrf_blob'] | 80 csrf_blob = post_json['csrf_blob'] |
57 | 81 |
58 if not secure.check_csrf_blob(csrf_blob): | 82 if not secure.check_csrf_blob(csrf_blob): |
59 bottle.response.status = 403 | 83 response.status = 403 |
60 return "Bad csrf" | 84 return "Bad csrf" |
61 | 85 |
62 ret = log.update_params(post_json['params']) | 86 ret = log.update_params(post_json['params']) |
63 if not ret is True: | 87 if not ret is True: |
64 bottle.response.status = 403 | 88 response.status = 409 # Conflict |
65 return ret | 89 return ret |
66 | 90 |
67 return "Good" | 91 return "Good" |
68 | 92 |
69 @route('/set') | 93 @route('/set') |
70 def set(): | 94 def set(): |
71 allowed = ["false", "true"][secure.check_user_hash(config.ALLOWED_USERS)] | 95 cookie_hash = secure.init_cookie() |
96 allowed = ["false", "true"][secure.check_cookie(config.ALLOWED_USERS)] | |
72 response.set_header('Cache-Control', 'no-cache') | 97 response.set_header('Cache-Control', 'no-cache') |
98 if request.query.fake: | |
99 inline_data = log.fake_params() | |
100 else: | |
101 inline_data = log.get_params() | |
102 if not inline_data: | |
103 response.status = 503 # Service Unavailable | |
104 return bottle.template('noparamsyet') | |
105 | |
73 return bottle.template('set', | 106 return bottle.template('set', |
74 inline_data = log.get_params(), | 107 inline_data = inline_data, |
75 csrf_blob = secure.get_csrf_blob(), | 108 csrf_blob = secure.get_csrf_blob(), |
76 allowed = allowed) | 109 allowed = allowed, |
77 | 110 cookie_hash = cookie_hash, |
78 @route('/set_current.json') | 111 email = urllib.quote(config.EMAIL)) |
79 def set_fresh(): | 112 |
80 response.set_header('Content-Type', 'application/javascript') | 113 def get_request_zoom(): |
81 return log.get_current() | 114 """ returns (length, end) tuple. |
82 | 115 length is in minutes, end is a DATE_FORMAT string """ |
83 @route('/') | |
84 def top(): | |
85 | |
86 minutes = int(request.query.get('length', 26*60)) | 116 minutes = int(request.query.get('length', 26*60)) |
87 | 117 |
88 if 'end' in request.query: | 118 if 'end' in request.query: |
89 end = datetime.strptime(request.query.end, DATE_FORMAT) | 119 end = datetime.strptime(request.query.end, DATE_FORMAT) |
90 else: | 120 else: |
91 end = datetime.now() | 121 end = datetime.now() |
92 | 122 |
93 if 'zoom' in request.query: | 123 if 'zoom' in request.query: |
94 orig_start = end - timedelta(minutes=minutes) | 124 orig_start = end - timedelta(minutes=minutes) |
95 orig_end = end | 125 orig_end = end |
96 xpos = int(request.query.x) | 126 scale = float(request.query.scaledwidth) / config.GRAPH_WIDTH |
127 xpos = int(request.query.x) / scale | |
97 xpos -= config.GRAPH_LEFT_MARGIN * config.ZOOM | 128 xpos -= config.GRAPH_LEFT_MARGIN * config.ZOOM |
98 | 129 |
99 if xpos >= 0 and xpos < config.GRAPH_WIDTH * config.ZOOM: | 130 if xpos >= 0 and xpos < config.GRAPH_WIDTH * config.ZOOM: |
100 click_time = orig_start \ | 131 click_time = orig_start \ |
101 + timedelta(minutes=(float(xpos) / (config.GRAPH_WIDTH * config.ZOOM)) * minutes) | 132 + timedelta(minutes=(float(xpos) / (config.GRAPH_WIDTH * config.ZOOM)) * minutes) |
107 minutes = int(minutes*ZOOM_SCALE) | 138 minutes = int(minutes*ZOOM_SCALE) |
108 end += timedelta(minutes=minutes/2) | 139 end += timedelta(minutes=minutes/2) |
109 | 140 |
110 if end > datetime.now(): | 141 if end > datetime.now(): |
111 end = datetime.now() | 142 end = datetime.now() |
112 | 143 |
144 endstr = end.strftime(DATE_FORMAT) | |
145 return (minutes, endstr) | |
146 | |
147 @route('/') | |
148 def top(): | |
149 minutes, endstr = get_request_zoom() | |
150 | |
113 request.query.replace('length', minutes) | 151 request.query.replace('length', minutes) |
114 request.query.replace('end', end.strftime(DATE_FORMAT)) | 152 request.query.replace('end', endstr) |
115 | 153 |
116 urlparams = urllib.urlencode(request.query) | 154 urlparams = urllib.urlencode(request.query) |
155 graphdata = encode_data(make_graph(minutes, endstr), 'image/png') | |
117 return bottle.template('top', urlparams=urlparams, | 156 return bottle.template('top', urlparams=urlparams, |
118 end = end.strftime(DATE_FORMAT), | 157 end = endstr, |
119 length = minutes) | 158 length = minutes, |
159 graphwidth = config.GRAPH_WIDTH, | |
160 graphdata = graphdata) | |
120 | 161 |
121 @route('/debug') | 162 @route('/debug') |
122 def debuglog(): | 163 def debuglog(): |
123 response.set_header('Content-Type', 'text/plain') | 164 response.set_header('Content-Type', 'text/plain') |
124 return log.tail_debug_log() | 165 return log.tail_debug_log() |
131 #return str(request.environ) | 172 #return str(request.environ) |
132 #yield "\n" | 173 #yield "\n" |
133 #var_lookup = environ['mod_ssl.var_lookup'] | 174 #var_lookup = environ['mod_ssl.var_lookup'] |
134 #return var_lookup("SSL_SERVER_I_DN_O") | 175 #return var_lookup("SSL_SERVER_I_DN_O") |
135 | 176 |
177 @route('/h') | |
178 def headers(): | |
179 response.set_header('Content-Type', 'text/plain') | |
180 return '\n'.join("%s: %s" % x for x in request.headers.items()) | |
181 | |
182 @route('/get_settings') | |
183 def get_settings(): | |
184 response.set_header('Cache-Control', 'no-cache') | |
185 req_etag = request.headers.get('etag', None) | |
186 if req_etag: | |
187 # wait for it to change | |
188 # XXX this is meant to return True if it has been woken up | |
189 # but it isn't working. Instead compare epochtag below. | |
190 log.fridge_settings.wait(req_etag, timeout=config.LONG_POLL_TIMEOUT) | |
191 | |
192 contents, epoch_tag = log.fridge_settings.get() | |
193 if epoch_tag == req_etag: | |
194 response.status = 304 | |
195 return "Nothing happened" | |
196 | |
197 response.set_header('Content-Type', 'application/json') | |
198 return json.dumps({'params': contents, 'epoch_tag': epoch_tag}) | |
199 | |
136 @bottle.get('/<filename:re:.*\.js>') | 200 @bottle.get('/<filename:re:.*\.js>') |
137 def javascripts(filename): | 201 def javascripts(filename): |
138 response.set_header('Cache-Control', "public, max-age=1296000") | 202 response.set_header('Cache-Control', "public, max-age=1296000") |
139 return bottle.static_file(filename, root='static') | 203 return bottle.static_file(filename, root='static') |
140 | 204 |
141 secure.setup_csrf() | |
142 | 205 |
143 def main(): | 206 def main(): |
207 """ for standalone testing """ | |
144 #bottle.debug(True) | 208 #bottle.debug(True) |
145 #bottle.run(reloader=True) | 209 #bottle.run(reloader=True) |
146 bottle.run(server='cgi', reloader=True) | 210 bottle.run(server='cgi', reloader=True) |
147 #bottle.run(port=9999, reloader=True) | 211 #bottle.run(port=9999, reloader=True) |
148 | 212 |