From 28b5e5af70e0baf3529dfe89769fcafc38cb48ff Mon Sep 17 00:00:00 2001 From: John Donaldson Date: Sun, 9 Sep 2018 10:34:48 -0500 Subject: [PATCH 1/9] Renamed LLNL scoreboard names, consolidated scoreboard JS --- ...oard-llnl-all.html => scoreboard-all.html} | 2 +- www/scoreboard-llnl.js | 528 ------------------ www/scoreboard-proj.html | 2 +- ...timeline.html => scoreboard-timeline.html} | 2 +- www/scoreboard.html | 5 +- www/scoreboard.js | 494 ++++++++++++++-- 6 files changed, 460 insertions(+), 573 deletions(-) rename www/{scoreboard-llnl-all.html => scoreboard-all.html} (93%) delete mode 100644 www/scoreboard-llnl.js rename www/{scoreboard-llnl-timeline.html => scoreboard-timeline.html} (95%) diff --git a/www/scoreboard-llnl-all.html b/www/scoreboard-all.html similarity index 93% rename from www/scoreboard-llnl-all.html rename to www/scoreboard-all.html index 4050e3f..e975d0e 100644 --- a/www/scoreboard-llnl-all.html +++ b/www/scoreboard-all.html @@ -14,7 +14,7 @@ } - + - + - `, - ) -} - -func staticPuzzleList(w http.ResponseWriter) { - ShowHtml( - w, Success, - "Open Puzzles", - ` -
-
-
- - `, - ) -} - -func staticPuzzle(w http.ResponseWriter) { - ShowHtml( - w, Success, - "Open Puzzles", - ` -
-
Loading...
-
-
- - - Team ID:
- Answer:
- -
- - `, - ) -} - -func tryServeFile(w http.ResponseWriter, req *http.Request, path string) bool { - f, err := os.Open(path) - if err != nil { - return false - } - defer f.Close() - - d, err := f.Stat() - if err != nil { - return false - } - - http.ServeContent(w, req, path, d.ModTime(), f) - return true -} - -func ServeStatic(w http.ResponseWriter, req *http.Request, resourcesDir string) { - path := req.URL.Path - if strings.Contains(path, "..") { - http.Error(w, "Invalid URL path", http.StatusBadRequest) - return - } - if path == "/" { - path = "/index.html" - } - - fpath := filepath.Join(resourcesDir, path) - if tryServeFile(w, req, fpath) { - return - } - - switch path { - case "/basic.css": - staticStylesheet(w) - case "/index.html": - staticIndex(w) - case "/scoreboard.html": - staticScoreboard(w) - case "/puzzle-list.html": - staticPuzzleList(w) - case "/puzzle.html": - staticPuzzle(w) - default: - http.NotFound(w, req) - } -} diff --git a/res/basic.css b/theme/basic.css similarity index 100% rename from res/basic.css rename to theme/basic.css diff --git a/res/index.html b/theme/index.html similarity index 100% rename from res/index.html rename to theme/index.html diff --git a/res/puzzle-list.html b/theme/puzzle-list.html similarity index 100% rename from res/puzzle-list.html rename to theme/puzzle-list.html diff --git a/res/puzzle.html b/theme/puzzle.html similarity index 100% rename from res/puzzle.html rename to theme/puzzle.html diff --git a/res/scoreboard.html b/theme/scoreboard.html similarity index 100% rename from res/scoreboard.html rename to theme/scoreboard.html From 138a5977dd481104b15607c91c408e38eec67dec Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Fri, 28 Sep 2018 17:57:03 +0000 Subject: [PATCH 6/9] We now make mothballs --- devel/package-puzzles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devel/package-puzzles.py b/devel/package-puzzles.py index 4a8da80..cbd7429 100755 --- a/devel/package-puzzles.py +++ b/devel/package-puzzles.py @@ -98,7 +98,7 @@ def build_category(categorydir, outdir): categoryname = os.path.basename(categorydir.strip(os.sep)) seedfn = os.path.join("category_seed.txt") - zipfilename = os.path.join(outdir, "%s.zip" % categoryname) + zipfilename = os.path.join(outdir, "%s.mb" % categoryname) logging.info("Building {} from {}".format(zipfilename, categorydir)) if os.path.exists(zipfilename): From 65daa41ca1d39c86907cd97aa4cd53b7972b08f6 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Fri, 28 Sep 2018 18:15:38 +0000 Subject: [PATCH 7/9] Bring back dev server updates --- devel/devel-server.py | 34 +++++++- devel/mothballer.py | 176 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 3 deletions(-) create mode 100755 devel/mothballer.py diff --git a/devel/devel-server.py b/devel/devel-server.py index 354f899..4db0b4a 100755 --- a/devel/devel-server.py +++ b/devel/devel-server.py @@ -1,6 +1,7 @@ #!/usr/bin/python3 import asyncio +import cgitb import glob import html from aiohttp import web @@ -15,11 +16,12 @@ import shutil import socketserver import sys import traceback +import mothballer sys.dont_write_bytecode = True # Don't write .pyc files def mkseed(): - return bytes(random.choice(b'abcdef0123456789') for i in range(40)) + return bytes(random.choice(b'abcdef0123456789') for i in range(40)).decode('ascii') class Page: def __init__(self, title, depth=0): @@ -72,11 +74,17 @@ async def handle_front(request): return p.response(request) async def handle_puzzlelist(request): + seed = request.query.get("seed", mkseed()) p = Page("Puzzle Categories", 1) + p.write("

seed = {}

".format(seed)) p.write("
    ") for i in sorted(glob.glob(os.path.join(request.app["puzzles_dir"], "*", ""))): bn = os.path.basename(i.strip('/\\')) - p.write('
  • puzzles/{}/
  • '.format(bn, bn)) + p.write("
  • ") + p.write("[mb]".format(cat=bn, seed=seed)) + p.write(" ") + p.write("{cat}".format(cat=bn, seed=seed)) + p.write("
  • ") p.write("
") return p.response(request) @@ -137,7 +145,7 @@ async def handle_puzzle(request): return p.response(request) async def handle_puzzlefile(request): - seed = request.query.get("seed", mkseed()) + seed = request.query.get("seed", mkseed()).encode('ascii') category = request.match_info.get("category") points = int(request.match_info.get("points")) filename = request.match_info.get("filename") @@ -158,6 +166,25 @@ async def handle_puzzlefile(request): resp.body = file.stream.read() return resp +async def handle_mothballer(request): + seed = request.query.get("seed", mkseed()) + category = request.match_info.get("category") + + try: + catdir = os.path.join(request.app["puzzles_dir"], category) + mb = mothballer.package(category, catdir, seed) + except: + body = cgitb.html(sys.exc_info()) + resp = web.Response(text=body, content_type="text/html") + return resp + + mb_buf = mb.read() + resp = web.Response( + body=mb_buf, + headers={"Content-Disposition": "attachment; filename={}.mb".format(category)}, + content_type="application/octet_stream", + ) + return resp if __name__ == '__main__': import argparse @@ -192,5 +219,6 @@ if __name__ == '__main__': app.router.add_route("GET", "/puzzles/{category}/", handle_category) app.router.add_route("GET", "/puzzles/{category}/{points}/", handle_puzzle) app.router.add_route("GET", "/puzzles/{category}/{points}/{filename}", handle_puzzlefile) + app.router.add_route("GET", "/mothballer/{category}", handle_mothballer) app.router.add_static("/files/", mydir, show_index=True) web.run_app(app, host=addr, port=port) diff --git a/devel/mothballer.py b/devel/mothballer.py new file mode 100755 index 0000000..f0799b1 --- /dev/null +++ b/devel/mothballer.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 + +import argparse +import binascii +import hashlib +import io +import json +import logging +import moth +import os +import shutil +import tempfile +import zipfile + +SEEDFN = "SEED" + + +def write_kv_pairs(ziphandle, filename, kv): + """ Write out a sorted map to file + :param ziphandle: a zipfile object + :param filename: The filename to write within the zipfile object + :param kv: the map to write out + :return: + """ + filehandle = io.StringIO() + for key in sorted(kv.keys()): + if isinstance(kv[key], list): + for val in kv[key]: + filehandle.write("%s %s\n" % (key, val)) + else: + filehandle.write("%s %s\n" % (key, kv[key])) + filehandle.seek(0) + ziphandle.writestr(filename, filehandle.read()) + + +def escape(s): + return s.replace('&', '&').replace('<', '<').replace('>', '>') + + +def generate_html(ziphandle, puzzle, puzzledir, category, points, authors, files): + html_content = io.StringIO() + file_content = io.StringIO() + if files: + file_content.write( +'''
+

Associated files:

+
    +''') + for fn in files: + file_content.write('
  • {efn}
  • \n'.format(fn=fn, efn=escape(fn))) + file_content.write( +'''
+
+''') + scripts = [''.format(s) for s in puzzle.scripts] + + html_content.write( +''' + + + + + {category} {points} + + {scripts} + + +

{category} for {points} points

+
+{body}
+{file_content}
+
+ + +
Team hash:
+
Answer:
+ +
+
+
Puzzle by {authors}
+ +'''.format( + category=category, + points=points, + body=puzzle.html_body(), + file_content=file_content.getvalue(), + authors=', '.join(authors), + scripts='\n'.join(scripts), + ) + ) + ziphandle.writestr(os.path.join(puzzledir, 'index.html'), html_content.getvalue()) + + +def build_category(categorydir, outdir): + category_seed = binascii.b2a_hex(os.urandom(20)) + + categoryname = os.path.basename(categorydir.strip(os.sep)) + zipfilename = os.path.join(outdir, "%s.mb" % categoryname) + logging.info("Building {} from {}".format(zipfilename, categorydir)) + + if os.path.exists(zipfilename): + # open and gather some state + existing = zipfile.ZipFile(zipfilename, 'r') + try: + category_seed = existing.open(SEEDFN).read().strip() + except Exception: + pass + existing.close() + logging.debug("Using PRNG seed {}".format(category_seed)) + + zipfileraw = tempfile.NamedTemporaryFile(delete=False) + mothball = package(categoryname, categorydir, category_seed) + shutil.copyfileobj(mothball, zipfileraw) + zipfileraw.close() + shutil.move(zipfileraw.name, zipfilename) + + +# Returns a file-like object containing the contents of the new zip file +def package(categoryname, categorydir, seed): + zfraw = io.BytesIO() + zf = zipfile.ZipFile(zfraw, 'x') + zf.writestr("category_seed.txt", seed) + + cat = moth.Category(categorydir, seed) + mapping = {} + answers = {} + summary = {} + for puzzle in cat: + logging.info("Processing point value {}".format(puzzle.points)) + + hashmap = hashlib.sha1(seed.encode('utf-8')) + hashmap.update(str(puzzle.points).encode('utf-8')) + puzzlehash = hashmap.hexdigest() + + mapping[puzzle.points] = puzzlehash + answers[puzzle.points] = puzzle.answers + summary[puzzle.points] = puzzle.summary + + puzzledir = os.path.join('content', puzzlehash) + files = [] + for fn, f in puzzle.files.items(): + if f.visible: + files.append(fn) + payload = f.stream.read() + zf.writestr(os.path.join(puzzledir, fn), payload) + + puzzledict = { + 'authors': puzzle.authors, + 'hashes': puzzle.hashes(), + 'files': files, + 'body': puzzle.html_body(), + } + puzzlejson = json.dumps(puzzledict) + zf.writestr(os.path.join(puzzledir, 'puzzle.json'), puzzlejson) + generate_html(zf, puzzle, puzzledir, categoryname, puzzle.points, puzzle.get_authors(), files) + + write_kv_pairs(zf, 'map.txt', mapping) + write_kv_pairs(zf, 'answers.txt', answers) + write_kv_pairs(zf, 'summaries.txt', summary) + + # clean up + zf.close() + zfraw.seek(0) + return zfraw + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Build a category package') + parser.add_argument('outdir', help='Output directory') + parser.add_argument('categorydirs', nargs='+', help='Directory of category source') + args = parser.parse_args() + + logging.basicConfig(level=logging.DEBUG) + + for categorydir in args.categorydirs: + build_category(categorydir, args.outdir) From e390c4443e8ed1cbeb1d8a70c8038207cbafa7e2 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 2 Oct 2018 15:22:09 +0000 Subject: [PATCH 8/9] Fixes from SECOR spark * Puzzle list word-wraps * Puzzle pull mutation handler can cope with non-HTML nodes --- theme/puzzle-list.html | 33 +++---- theme/puzzle.html | 67 +++++++------- theme/scoreboard.html | 196 ++++++++++++++++++++--------------------- 3 files changed, 150 insertions(+), 146 deletions(-) diff --git a/theme/puzzle-list.html b/theme/puzzle-list.html index 32f79dc..d841bb7 100644 --- a/theme/puzzle-list.html +++ b/theme/puzzle-list.html @@ -35,32 +35,33 @@ function render(obj) { var id = puzzle[1]; var i = document.createElement('li'); + i.textContent = " "; l.appendChild(i); if (points === 0) { - i.textContent = "✿"; + i.textContent = "✿"; } else { - var a = document.createElement('a'); - i.appendChild(a); - a.textContent = points; + var a = document.createElement('a'); + i.appendChild(a); + a.textContent = points; a.href = "puzzle.html?cat=" + cat + "&points=" + points + "&pid=" + id; - } - } + } + } - puzzlesElement.appendChild(pdiv); - document.getElementById("puzzles").appendChild(puzzlesElement); + puzzlesElement.appendChild(pdiv); + document.getElementById("puzzles").appendChild(puzzlesElement); } } function init() { - fetch("puzzles.json") - .then(function(resp) { - return resp.json(); - }).then(function(obj) { - render(obj); - }).catch(function(err) { - console.log("Error", err); - }); + fetch("puzzles.json") + .then(function(resp) { + return resp.json(); + }).then(function(obj) { + render(obj); + }).catch(function(err) { + console.log("Error", err); + }); } document.addEventListener("DOMContentLoaded", init); diff --git a/theme/puzzle.html b/theme/puzzle.html index 87723b8..b36e6b5 100644 --- a/theme/puzzle.html +++ b/theme/puzzle.html @@ -6,6 +6,7 @@ + @@ -75,17 +77,18 @@ document.addEventListener("DOMContentLoaded", init);

    Puzzle by

    -
    - - - Team ID:
    - Answer:
    - -
    +
    + + + Team ID:
    + Answer:
    + +
    diff --git a/theme/scoreboard.html b/theme/scoreboard.html index a50d9f1..1cff569 100644 --- a/theme/scoreboard.html +++ b/theme/scoreboard.html @@ -8,126 +8,126 @@