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