diff 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
line wrap: on
line diff
--- a/web/templog.py	Thu Mar 19 21:50:52 2015 +0800
+++ b/web/templog.py	Sat Jul 06 18:29:45 2019 +0800
@@ -11,6 +11,7 @@
 import os
 import traceback
 import fcntl
+import hashlib
 
 import bottle
 from bottle import route, request, response
@@ -23,12 +24,22 @@
 DATE_FORMAT = '%Y%m%d-%H.%M'
 ZOOM_SCALE = 2.0
 
+class TemplogBottle(bottle.Bottle):
+    def run(*args, **argm):
+        argm['server'] = 'gevent'
+        super(TemplogBottle, self).run(*args, **argm)
+
+bottle.default_app.push(TemplogBottle())
+
+secure.setup_csrf()
+
 @route('/update', method='post')
 def update():
     js_enc = request.forms.data
     mac = request.forms.hmac
 
-    if hmac.new(config.HMAC_KEY, js_enc).hexdigest() != mac:
+    h = hmac.new(config.HMAC_KEY, js_enc.strip(), hashlib.sha256).hexdigest()
+    if h != mac:
         raise bottle.HTTPError(code = 403, output = "Bad key")
 
     js = zlib.decompress(binascii.a2b_base64(js_enc))
@@ -39,50 +50,69 @@
 
     return "OK"
 
-@route('/graph.png')
-def graph():
-    length_minutes = int(request.query.length)
-    end = datetime.strptime(request.query.end, DATE_FORMAT)
+def make_graph(length, end):
+    length_minutes = int(length)
+    end = datetime.strptime(end, DATE_FORMAT)
     start = end - timedelta(minutes=length_minutes)
 
-    response.set_header('Content-Type', 'image/png')
     start_epoch = time.mktime(start.timetuple())
     return log.graph_png(start_epoch, length_minutes * 60)
 
+def encode_data(data, mimetype):
+    return 'data:%s;base64,%s' % (mimetype, binascii.b2a_base64(data).rstrip())
+
+@route('/graph.png')
+def graph():
+    response.set_header('Content-Type', 'image/png')
+    minutes, endstr = get_request_zoom()
+    return make_graph(minutes, endstr)
+
 @route('/set/update', method='post')
 def set_update():
+    if not secure.check_cookie(config.ALLOWED_USERS):
+        # the "Save" button should be disabled if the cert wasn't
+        # good
+        response.status = 403
+        return "No cert, dodginess"
+
     post_json = json.loads(request.forms.data)
 
     csrf_blob = post_json['csrf_blob']
 
     if not secure.check_csrf_blob(csrf_blob):
-        bottle.response.status = 403
+        response.status = 403
         return "Bad csrf"
 
     ret = log.update_params(post_json['params'])
     if not ret is True:
-        bottle.response.status = 403
+        response.status = 409 # Conflict
         return ret
         
     return "Good"
 
 @route('/set')
 def set():
-    allowed = ["false", "true"][secure.check_user_hash(config.ALLOWED_USERS)]
+    cookie_hash = secure.init_cookie()
+    allowed = ["false", "true"][secure.check_cookie(config.ALLOWED_USERS)]
     response.set_header('Cache-Control', 'no-cache')
-    return bottle.template('set', 
-        inline_data = log.get_params(), 
-        csrf_blob = secure.get_csrf_blob(),
-        allowed = allowed)
+    if request.query.fake:
+        inline_data = log.fake_params()
+    else:
+        inline_data = log.get_params()
+    if not inline_data:
+        response.status = 503 # Service Unavailable
+        return bottle.template('noparamsyet')
 
-@route('/set_current.json')
-def set_fresh():
-    response.set_header('Content-Type', 'application/javascript')
-    return log.get_current()
+    return bottle.template('set', 
+        inline_data = inline_data,
+        csrf_blob = secure.get_csrf_blob(),
+        allowed = allowed,
+        cookie_hash = cookie_hash,
+        email = urllib.quote(config.EMAIL))
 
-@route('/')
-def top():
-
+def get_request_zoom():
+    """ returns (length, end) tuple.
+    length is in minutes, end is a DATE_FORMAT string """
     minutes = int(request.query.get('length', 26*60))
 
     if 'end' in request.query:
@@ -93,7 +123,8 @@
     if 'zoom' in request.query:
         orig_start = end - timedelta(minutes=minutes)
         orig_end = end
-        xpos = int(request.query.x)
+        scale = float(request.query.scaledwidth) / config.GRAPH_WIDTH
+        xpos = int(request.query.x) / scale
         xpos -= config.GRAPH_LEFT_MARGIN * config.ZOOM
 
         if xpos >= 0 and xpos < config.GRAPH_WIDTH * config.ZOOM:
@@ -109,14 +140,24 @@
 
     if end > datetime.now():
         end = datetime.now()
-        
+
+    endstr = end.strftime(DATE_FORMAT)
+    return (minutes, endstr)
+
+@route('/')
+def top():
+    minutes, endstr = get_request_zoom()
+
     request.query.replace('length', minutes)
-    request.query.replace('end', end.strftime(DATE_FORMAT))
+    request.query.replace('end', endstr)
 
     urlparams = urllib.urlencode(request.query)
+    graphdata = encode_data(make_graph(minutes, endstr), 'image/png')
     return bottle.template('top', urlparams=urlparams,
-                    end = end.strftime(DATE_FORMAT),
-                    length = minutes)
+                    end = endstr,
+                    length = minutes,
+                    graphwidth = config.GRAPH_WIDTH,
+                    graphdata = graphdata)
 
 @route('/debug')
 def debuglog():
@@ -133,14 +174,37 @@
     #var_lookup = environ['mod_ssl.var_lookup']
     #return var_lookup("SSL_SERVER_I_DN_O")
 
+@route('/h')
+def headers():
+    response.set_header('Content-Type', 'text/plain')
+    return '\n'.join("%s: %s" % x for x in request.headers.items())
+
+@route('/get_settings')
+def get_settings():
+    response.set_header('Cache-Control', 'no-cache')
+    req_etag = request.headers.get('etag', None)
+    if req_etag:
+        # wait for it to change
+        # XXX this is meant to return True if it has been woken up
+        # but it isn't working. Instead compare epochtag below.
+        log.fridge_settings.wait(req_etag, timeout=config.LONG_POLL_TIMEOUT)
+
+    contents, epoch_tag = log.fridge_settings.get()
+    if epoch_tag == req_etag:
+        response.status = 304
+        return "Nothing happened"
+
+    response.set_header('Content-Type', 'application/json')
+    return json.dumps({'params': contents, 'epoch_tag': epoch_tag})
+
 @bottle.get('/<filename:re:.*\.js>')
 def javascripts(filename):
     response.set_header('Cache-Control', "public, max-age=1296000")
     return bottle.static_file(filename, root='static')
 
-secure.setup_csrf()
 
 def main():
+    """ for standalone testing """
     #bottle.debug(True)
     #bottle.run(reloader=True)
     bottle.run(server='cgi', reloader=True)