Mercurial > templog
annotate web/templog.py @ 253:0a1b642e3086
long polling config updates
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Mon, 08 Jun 2015 22:29:46 +0800 |
parents | 141948a400a6 |
children | 8ef52f27cf95 |
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 |
69 | 9 import urllib |
10 import sys | |
182 | 11 import os |
185 | 12 import traceback |
13 import fcntl | |
240 | 14 import hashlib |
28 | 15 |
27 | 16 import bottle |
31
5e75e08d20ac
- Various fixes for web server, kind of works
Matt Johnston <matt@ucc.asn.au>
parents:
30
diff
changeset
|
17 from bottle import route, request, response |
27 | 18 |
28 | 19 import config |
20 import log | |
185 | 21 import secure |
191 | 22 import atomicfile |
28 | 23 |
69 | 24 DATE_FORMAT = '%Y%m%d-%H.%M' |
25 ZOOM_SCALE = 2.0 | |
26 | |
237 | 27 class TemplogBottle(bottle.Bottle): |
28 def run(*args, **argm): | |
29 argm['server'] = 'gevent' | |
30 super(TemplogBottle, self).run(*args, **argm) | |
31 | |
253
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
32 bottle.default_app.push(TemplogBottle()) |
237 | 33 |
34 secure.setup_csrf() | |
35 | |
27 | 36 @route('/update', method='post') |
37 def update(): | |
146
3b4277aaed3c
update web to handle new style params
Matt Johnston <matt@ucc.asn.au>
parents:
103
diff
changeset
|
38 js_enc = request.forms.data |
28 | 39 mac = request.forms.hmac |
40 | |
240 | 41 h = hmac.new(config.HMAC_KEY, js_enc.strip(), hashlib.sha256).hexdigest() |
42 if h != mac: | |
85 | 43 raise bottle.HTTPError(code = 403, output = "Bad key") |
28 | 44 |
146
3b4277aaed3c
update web to handle new style params
Matt Johnston <matt@ucc.asn.au>
parents:
103
diff
changeset
|
45 js = zlib.decompress(binascii.a2b_base64(js_enc)) |
28 | 46 |
146
3b4277aaed3c
update web to handle new style params
Matt Johnston <matt@ucc.asn.au>
parents:
103
diff
changeset
|
47 params = json.loads(js) |
3b4277aaed3c
update web to handle new style params
Matt Johnston <matt@ucc.asn.au>
parents:
103
diff
changeset
|
48 |
3b4277aaed3c
update web to handle new style params
Matt Johnston <matt@ucc.asn.au>
parents:
103
diff
changeset
|
49 log.parse(params) |
28 | 50 |
51 return "OK" | |
27 | 52 |
244 | 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 | |
27 | 64 @route('/graph.png') |
65 def graph(): | |
30
13fcf497f8b7
parse the arguments for start/length
Matt Johnston <matt@ucc.asn.au>
parents:
29
diff
changeset
|
66 response.set_header('Content-Type', 'image/png') |
250 | 67 minutes, endstr = get_request_zoom() |
68 return make_graph(minutes, endstr) | |
27 | 69 |
188 | 70 @route('/set/update', method='post') |
194 | 71 def set_update(): |
188 | 72 post_json = json.loads(request.forms.data) |
73 | |
74 csrf_blob = post_json['csrf_blob'] | |
75 | |
194 | 76 if not secure.check_csrf_blob(csrf_blob): |
253
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
77 response.status = 403 |
194 | 78 return "Bad csrf" |
79 | |
80 ret = log.update_params(post_json['params']) | |
81 if not ret is True: | |
253
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
82 response.status = 403 |
194 | 83 return ret |
84 | |
85 return "Good" | |
188 | 86 |
182 | 87 @route('/set') |
88 def set(): | |
211
59379b2bd056
key fingerprints are case- and whitespace-insensitive.
Matt Johnston <matt@ucc.asn.au>
parents:
202
diff
changeset
|
89 allowed = ["false", "true"][secure.check_user_hash(config.ALLOWED_USERS)] |
202
6dd157a12035
Add url link, improve atomicfile
Matt Johnston <matt@ucc.asn.au>
parents:
194
diff
changeset
|
90 response.set_header('Cache-Control', 'no-cache') |
185 | 91 return bottle.template('set', |
92 inline_data = log.get_params(), | |
189 | 93 csrf_blob = secure.get_csrf_blob(), |
94 allowed = allowed) | |
182 | 95 |
250 | 96 def get_request_zoom(): |
97 """ returns (length, end) tuple. | |
98 length is in minutes, end is a DATE_FORMAT string """ | |
69 | 99 minutes = int(request.query.get('length', 26*60)) |
100 | |
101 if 'end' in request.query: | |
102 end = datetime.strptime(request.query.end, DATE_FORMAT) | |
103 else: | |
104 end = datetime.now() | |
105 | |
106 if 'zoom' in request.query: | |
107 orig_start = end - timedelta(minutes=minutes) | |
108 orig_end = end | |
249 | 109 scale = float(request.query.scaledwidth) / config.GRAPH_WIDTH |
110 xpos = int(request.query.x) / scale | |
77 | 111 xpos -= config.GRAPH_LEFT_MARGIN * config.ZOOM |
69 | 112 |
77 | 113 if xpos >= 0 and xpos < config.GRAPH_WIDTH * config.ZOOM: |
69 | 114 click_time = orig_start \ |
77 | 115 + timedelta(minutes=(float(xpos) / (config.GRAPH_WIDTH * config.ZOOM)) * minutes) |
69 | 116 minutes = int(minutes / ZOOM_SCALE) |
117 | |
118 end = click_time + timedelta(minutes=minutes/2) | |
119 else: | |
120 # zoom out | |
121 minutes = int(minutes*ZOOM_SCALE) | |
122 end += timedelta(minutes=minutes/2) | |
123 | |
124 if end > datetime.now(): | |
125 end = datetime.now() | |
250 | 126 |
127 endstr = end.strftime(DATE_FORMAT) | |
128 return (minutes, endstr) | |
129 | |
130 @route('/') | |
131 def top(): | |
132 minutes, endstr = get_request_zoom() | |
133 | |
69 | 134 request.query.replace('length', minutes) |
250 | 135 request.query.replace('end', endstr) |
69 | 136 |
137 urlparams = urllib.urlencode(request.query) | |
250 | 138 graphdata = encode_data(make_graph(minutes, endstr), 'image/png') |
69 | 139 return bottle.template('top', urlparams=urlparams, |
250 | 140 end = endstr, |
244 | 141 length = minutes, |
142 graphwidth = config.GRAPH_WIDTH, | |
143 graphdata = graphdata) | |
27 | 144 |
103 | 145 @route('/debug') |
146 def debuglog(): | |
147 response.set_header('Content-Type', 'text/plain') | |
148 return log.tail_debug_log() | |
37
8da0fdadc8d7
- Getting there, update has problems
Matt Johnston <matt@ucc.asn.au>
parents:
31
diff
changeset
|
149 |
182 | 150 @route('/env') |
151 def env(): | |
152 response.set_header('Content-Type', 'text/plain') | |
189 | 153 #return '\n'.join(traceback.format_stack()) |
154 return '\n'.join(("%s %s" % k) for k in request.environ.items()) | |
182 | 155 #return str(request.environ) |
156 #yield "\n" | |
157 #var_lookup = environ['mod_ssl.var_lookup'] | |
158 #return var_lookup("SSL_SERVER_I_DN_O") | |
159 | |
253
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
160 @route('/get_settings') |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
161 def get_settings(): |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
162 response.set_header('Cache-Control', 'no-cache') |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
163 req_etag = request.headers.get('etag', None) |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
164 if req_etag: |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
165 # wait for it to change |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
166 if not log.fridge_settings.wait(req_etag, timeout=LONG_POLL_TIMEOUT): |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
167 response.status = 304 |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
168 return "Nothing happened" |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
169 |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
170 response.set_header('Content-Type', 'application/json') |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
171 contents, epoch_tag = client_settings.get() |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
172 return json.dumps({'params': contents, 'epoch_tag': epoch_tag}) |
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
173 |
182 | 174 @bottle.get('/<filename:re:.*\.js>') |
175 def javascripts(filename): | |
185 | 176 response.set_header('Cache-Control', "public, max-age=1296000") |
182 | 177 return bottle.static_file(filename, root='static') |
178 | |
253
0a1b642e3086
long polling config updates
Matt Johnston <matt@ucc.asn.au>
parents:
250
diff
changeset
|
179 |
27 | 180 def main(): |
61 | 181 #bottle.debug(True) |
182 #bottle.run(reloader=True) | |
183 bottle.run(server='cgi', reloader=True) | |
40 | 184 #bottle.run(port=9999, reloader=True) |
27 | 185 |
186 if __name__ == '__main__': | |
187 main() | |
188 |