Mercurial > templog
annotate web/templog.py @ 582:cebec9b40ad2
merge
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Mon, 30 Nov 2015 21:45:17 +0800 |
parents | 2773400f9d64 b4d6f1ae0b9e |
children | 8441916e3095 |
rev | line source |
---|---|
333 | 1 #!/usr/bin/env python2.7 |
2 | |
334 | 3 import binascii |
445
5b9dc87c988f
update web to handle new style params
Matt Johnston <matt@ucc.asn.au>
parents:
409
diff
changeset
|
4 import json |
334 | 5 import hmac |
335 | 6 import zlib |
375 | 7 from datetime import datetime, timedelta |
337
f575ef538f5d
- Various fixes for web server, kind of works
Matt Johnston <matt@ucc.asn.au>
parents:
336
diff
changeset
|
8 import time |
375 | 9 import urllib |
10 import sys | |
482 | 11 import os |
485 | 12 import traceback |
13 import fcntl | |
541 | 14 import hashlib |
334 | 15 |
333 | 16 import bottle |
337
f575ef538f5d
- Various fixes for web server, kind of works
Matt Johnston <matt@ucc.asn.au>
parents:
336
diff
changeset
|
17 from bottle import route, request, response |
333 | 18 |
334 | 19 import config |
20 import log | |
485 | 21 import secure |
489 | 22 import atomicfile |
334 | 23 |
375 | 24 DATE_FORMAT = '%Y%m%d-%H.%M' |
25 ZOOM_SCALE = 2.0 | |
26 | |
539 | 27 class TemplogBottle(bottle.Bottle): |
28 def run(*args, **argm): | |
29 argm['server'] = 'gevent' | |
30 super(TemplogBottle, self).run(*args, **argm) | |
31 | |
551
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
32 bottle.default_app.push(TemplogBottle()) |
539 | 33 |
34 secure.setup_csrf() | |
35 | |
333 | 36 @route('/update', method='post') |
37 def update(): | |
445
5b9dc87c988f
update web to handle new style params
Matt Johnston <matt@ucc.asn.au>
parents:
409
diff
changeset
|
38 js_enc = request.forms.data |
334 | 39 mac = request.forms.hmac |
40 | |
541 | 41 h = hmac.new(config.HMAC_KEY, js_enc.strip(), hashlib.sha256).hexdigest() |
42 if h != mac: | |
391 | 43 raise bottle.HTTPError(code = 403, output = "Bad key") |
334 | 44 |
445
5b9dc87c988f
update web to handle new style params
Matt Johnston <matt@ucc.asn.au>
parents:
409
diff
changeset
|
45 js = zlib.decompress(binascii.a2b_base64(js_enc)) |
334 | 46 |
445
5b9dc87c988f
update web to handle new style params
Matt Johnston <matt@ucc.asn.au>
parents:
409
diff
changeset
|
47 params = json.loads(js) |
5b9dc87c988f
update web to handle new style params
Matt Johnston <matt@ucc.asn.au>
parents:
409
diff
changeset
|
48 |
5b9dc87c988f
update web to handle new style params
Matt Johnston <matt@ucc.asn.au>
parents:
409
diff
changeset
|
49 log.parse(params) |
334 | 50 |
51 return "OK" | |
333 | 52 |
542 | 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()) | |
63 | |
333 | 64 @route('/graph.png') |
65 def graph(): | |
336
ba4c4df13487
parse the arguments for start/length
Matt Johnston <matt@ucc.asn.au>
parents:
335
diff
changeset
|
66 response.set_header('Content-Type', 'image/png') |
549 | 67 minutes, endstr = get_request_zoom() |
68 return make_graph(minutes, endstr) | |
333 | 69 |
487 | 70 @route('/set/update', method='post') |
492 | 71 def set_update(): |
573
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
72 if not secure.check_user_hash(config.ALLOWED_USERS): |
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
73 # the "Save" button should be disabled if the cert wasn't |
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
74 # good |
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
75 response.status = 403 |
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
76 return "No cert, dodginess" |
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
77 |
487 | 78 post_json = json.loads(request.forms.data) |
79 | |
80 csrf_blob = post_json['csrf_blob'] | |
81 | |
492 | 82 if not secure.check_csrf_blob(csrf_blob): |
551
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
83 response.status = 403 |
492 | 84 return "Bad csrf" |
85 | |
86 ret = log.update_params(post_json['params']) | |
87 if not ret is True: | |
573
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
88 response.status = 409 # Conflict |
492 | 89 return ret |
90 | |
91 return "Good" | |
487 | 92 |
482 | 93 @route('/set') |
94 def set(): | |
505
ad846b9bdd10
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
501
diff
changeset
|
95 allowed = ["false", "true"][secure.check_user_hash(config.ALLOWED_USERS)] |
501
236e5d131b3e
Add url link, improve atomicfile
Matt Johnston <matt@ucc.asn.au>
parents:
492
diff
changeset
|
96 response.set_header('Cache-Control', 'no-cache') |
581
b4d6f1ae0b9e
make overshoot_factor floating point
Matt Johnston <matt@ucc.asn.au>
parents:
573
diff
changeset
|
97 inline_data = log.get_params() |
b4d6f1ae0b9e
make overshoot_factor floating point
Matt Johnston <matt@ucc.asn.au>
parents:
573
diff
changeset
|
98 if not inline_data: |
b4d6f1ae0b9e
make overshoot_factor floating point
Matt Johnston <matt@ucc.asn.au>
parents:
573
diff
changeset
|
99 response.status = 503 # Service Unavailable |
b4d6f1ae0b9e
make overshoot_factor floating point
Matt Johnston <matt@ucc.asn.au>
parents:
573
diff
changeset
|
100 return bottle.template('noparamsyet') |
b4d6f1ae0b9e
make overshoot_factor floating point
Matt Johnston <matt@ucc.asn.au>
parents:
573
diff
changeset
|
101 |
485 | 102 return bottle.template('set', |
581
b4d6f1ae0b9e
make overshoot_factor floating point
Matt Johnston <matt@ucc.asn.au>
parents:
573
diff
changeset
|
103 inline_data = inline_data, |
488 | 104 csrf_blob = secure.get_csrf_blob(), |
105 allowed = allowed) | |
482 | 106 |
549 | 107 def get_request_zoom(): |
108 """ returns (length, end) tuple. | |
109 length is in minutes, end is a DATE_FORMAT string """ | |
375 | 110 minutes = int(request.query.get('length', 26*60)) |
111 | |
112 if 'end' in request.query: | |
113 end = datetime.strptime(request.query.end, DATE_FORMAT) | |
114 else: | |
115 end = datetime.now() | |
116 | |
117 if 'zoom' in request.query: | |
118 orig_start = end - timedelta(minutes=minutes) | |
119 orig_end = end | |
548 | 120 scale = float(request.query.scaledwidth) / config.GRAPH_WIDTH |
121 xpos = int(request.query.x) / scale | |
383 | 122 xpos -= config.GRAPH_LEFT_MARGIN * config.ZOOM |
375 | 123 |
383 | 124 if xpos >= 0 and xpos < config.GRAPH_WIDTH * config.ZOOM: |
375 | 125 click_time = orig_start \ |
383 | 126 + timedelta(minutes=(float(xpos) / (config.GRAPH_WIDTH * config.ZOOM)) * minutes) |
375 | 127 minutes = int(minutes / ZOOM_SCALE) |
128 | |
563 | 129 end = click_time + timedelta(minutes=minutes//2) |
375 | 130 else: |
131 # zoom out | |
132 minutes = int(minutes*ZOOM_SCALE) | |
563 | 133 end += timedelta(minutes=minutes//2) |
375 | 134 |
135 if end > datetime.now(): | |
136 end = datetime.now() | |
549 | 137 |
138 endstr = end.strftime(DATE_FORMAT) | |
139 return (minutes, endstr) | |
140 | |
141 @route('/') | |
142 def top(): | |
143 minutes, endstr = get_request_zoom() | |
144 | |
375 | 145 request.query.replace('length', minutes) |
549 | 146 request.query.replace('end', endstr) |
375 | 147 |
148 urlparams = urllib.urlencode(request.query) | |
549 | 149 graphdata = encode_data(make_graph(minutes, endstr), 'image/png') |
375 | 150 return bottle.template('top', urlparams=urlparams, |
549 | 151 end = endstr, |
542 | 152 length = minutes, |
153 graphwidth = config.GRAPH_WIDTH, | |
154 graphdata = graphdata) | |
333 | 155 |
409 | 156 @route('/debug') |
157 def debuglog(): | |
158 response.set_header('Content-Type', 'text/plain') | |
159 return log.tail_debug_log() | |
344
ea1779d27641
- Getting there, update has problems
Matt Johnston <matt@ucc.asn.au>
parents:
337
diff
changeset
|
160 |
482 | 161 @route('/env') |
162 def env(): | |
163 response.set_header('Content-Type', 'text/plain') | |
488 | 164 #return '\n'.join(traceback.format_stack()) |
165 return '\n'.join(("%s %s" % k) for k in request.environ.items()) | |
482 | 166 #return str(request.environ) |
167 #yield "\n" | |
168 #var_lookup = environ['mod_ssl.var_lookup'] | |
169 #return var_lookup("SSL_SERVER_I_DN_O") | |
170 | |
573
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
171 @route('/h') |
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
172 def headers(): |
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
173 response.set_header('Content-Type', 'text/plain') |
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
174 return '\n'.join("%s: %s" % x for x in request.headers.items()) |
a3e9c97c448b
Oops, we didn't authenticate the parameter update
Matt Johnston <matt@ucc.asn.au>
parents:
559
diff
changeset
|
175 |
551
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
176 @route('/get_settings') |
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
177 def get_settings(): |
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
178 response.set_header('Cache-Control', 'no-cache') |
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
179 req_etag = request.headers.get('etag', None) |
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
180 if req_etag: |
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
181 # wait for it to change |
559
d563e14da813
fix server side long polling
Matt Johnston <matt@ucc.asn.au>
parents:
553
diff
changeset
|
182 # XXX this is meant to return True if it has been woken up |
d563e14da813
fix server side long polling
Matt Johnston <matt@ucc.asn.au>
parents:
553
diff
changeset
|
183 # but it isn't working. Instead compare epochtag below. |
d563e14da813
fix server side long polling
Matt Johnston <matt@ucc.asn.au>
parents:
553
diff
changeset
|
184 log.fridge_settings.wait(req_etag, timeout=config.LONG_POLL_TIMEOUT) |
d563e14da813
fix server side long polling
Matt Johnston <matt@ucc.asn.au>
parents:
553
diff
changeset
|
185 |
d563e14da813
fix server side long polling
Matt Johnston <matt@ucc.asn.au>
parents:
553
diff
changeset
|
186 contents, epoch_tag = log.fridge_settings.get() |
d563e14da813
fix server side long polling
Matt Johnston <matt@ucc.asn.au>
parents:
553
diff
changeset
|
187 if epoch_tag == req_etag: |
d563e14da813
fix server side long polling
Matt Johnston <matt@ucc.asn.au>
parents:
553
diff
changeset
|
188 response.status = 304 |
d563e14da813
fix server side long polling
Matt Johnston <matt@ucc.asn.au>
parents:
553
diff
changeset
|
189 return "Nothing happened" |
551
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
190 |
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
191 response.set_header('Content-Type', 'application/json') |
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
192 return json.dumps({'params': contents, 'epoch_tag': epoch_tag}) |
552 | 193 |
482 | 194 @bottle.get('/<filename:re:.*\.js>') |
195 def javascripts(filename): | |
485 | 196 response.set_header('Cache-Control', "public, max-age=1296000") |
482 | 197 return bottle.static_file(filename, root='static') |
198 | |
551
9499bd2f344b
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
549
diff
changeset
|
199 |
333 | 200 def main(): |
552 | 201 """ for standalone testing """ |
367 | 202 #bottle.debug(True) |
203 #bottle.run(reloader=True) | |
204 bottle.run(server='cgi', reloader=True) | |
346 | 205 #bottle.run(port=9999, reloader=True) |
333 | 206 |
207 if __name__ == '__main__': | |
208 main() | |
209 |