From bffe0f2a9ee5d85599d875ad71946bca1e3bd9b9 Mon Sep 17 00:00:00 2001 From: Helmut Grohne Date: Tue, 5 Mar 2019 20:15:19 +0100 Subject: add initial web presentation --- webapp.py | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 webapp.py (limited to 'webapp.py') diff --git a/webapp.py b/webapp.py new file mode 100644 index 0000000..cfdf434 --- /dev/null +++ b/webapp.py @@ -0,0 +1,155 @@ +#!/usr/bin/python3 + +import datetime +import lzma + +import apt_pkg +apt_pkg.init() +version_compare = apt_pkg.version_compare + +import flask +import flask_sqlalchemy +import jinja2 +import sqlalchemy +import werkzeug + +app = flask.Flask("crossqa") +app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///db' +db = flask_sqlalchemy.SQLAlchemy(app) + +src_template = """ + + + + + {{ sourcepackage|e }} - Debian cross build +

{{ sourcepackage|e }}

+

Cross satisfiability

+ + + + + + {%- set okarchs = depresult.pop(None, None) -%} + {%- for reason, archs in depresult.items()|sort -%} + + + + + {%- endfor -%} + {%- if okarchs -%} + + + + + {%- endif -%} +
statearchitectures
{{ reason|e }}{{ archs|arch_format }}
ok{{ okarchs|arch_format }}
+
See also
+ + {%- if builds -%} +

Cross builds

+ + + + + + + + {%- for build in builds|sort(attribute='starttime', reverse=true) -%} + + + + + + + {%- endfor -%} +
startedversionarchitectureresult
+ + {{- build.starttime|sqltimestamp|formatts -}} + + {{ build.version|e }}{{ build.architecture|e }} + + {{- "ok" if build.success else "failed" -}} + + xz +
+ {%- endif -%} + + +""" + +@app.template_filter("sqltimestamp") +def sqltimestamp_filter(s): + return datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S.%f") + +@app.template_filter("formatts") +def formatts_filter(ts): + assert isinstance(ts, datetime.datetime) + dt = datetime.datetime.utcnow() - ts + if dt < datetime.timedelta(seconds=1): + return "now" + if dt < datetime.timedelta(seconds=100): + return "%d s" % dt.seconds + if dt < datetime.timedelta(minutes=100): + return "%d m" % (dt.seconds // 60) + if dt < datetime.timedelta(days=1): + return "%d h" % (dt.seconds // (60 * 60)) + return "%d d" % dt.days + +@app.template_filter('arch_format') +@jinja2.contextfilter +def arch_format_filter(context, some_archs): + if context["architectures"] == some_archs: + return "any" + if len(some_archs) > 3: + return jinja2.Markup('%d architectures' % + (", ".join(sorted(some_archs)), len(some_archs))) + return ", ".join(sorted(some_archs)) + +def collect_depstate(conn, source): + version = None + depstate = None + query = sqlalchemy.text(""" + SELECT version, architecture, satisfiable, reason + FROM depstate WHERE source = :source;""") + for row in conn.execute(query, source=source): + if version is None or version_compare(version, row.version) > 0: + version = row.version + depstate = {} + depstate[row.architecture] = None if row.satisfiable else row.reason + if version is None: + raise werkzeug.exceptions.NotFound() + depresult = {} + for arch, reason in depstate.items(): + depresult.setdefault(reason, set()).add(arch) + return version, depresult + +@app.route("/src/") +def show_source(source): + context = dict(sourcepackage=source) + with db.engine.connect() as conn: + query = sqlalchemy.text("SELECT architecture FROM depcheck;") + context["architectures"] = set(row[0] for row in conn.execute(query)) + context["version"], context["depresult"] = collect_depstate(conn, + source) + query = sqlalchemy.text(""" + SELECT version, architecture, success, starttime, filename + FROM builds WHERE source = :source;""") + context["builds"] = list(conn.execute(query, source=source)) + return flask.render_template_string(src_template, **context) + +@app.route("/build/") +def show_log(filename): + if filename.endswith(".xz"): + return flask.send_from_directory("logs", filename, + mimetype="application/octet-stream") + filename += ".xz" + return flask.send_file(lzma.open(flask.safe_join("logs", filename), "rb"), + mimetype="text/plain") -- cgit v1.2.3