diff options
| author | toufic ar <contact@toufy.me> | 2026-05-11 19:43:18 +0300 |
|---|---|---|
| committer | toufic ar <contact@toufy.me> | 2026-05-11 19:43:18 +0300 |
| commit | 1817d117d020318ba5fcd281708015eaad3f092c (patch) | |
| tree | d921291bbe95524eff1ff628591426e25541c0dc /web.py | |
| parent | ebbdc72f0a4c9bcf38526520a7728221f05eaedb (diff) | |
| download | makeshiftci-1817d117d020318ba5fcd281708015eaad3f092c.tar.gz makeshiftci-1817d117d020318ba5fcd281708015eaad3f092c.zip | |
initial web UI
Diffstat (limited to 'web.py')
| -rw-r--r-- | web.py | 151 |
1 files changed, 151 insertions, 0 deletions
@@ -0,0 +1,151 @@ +import json +import os +import queue +import re +import threading +import time +from typing import Any + +from flask import ( + Flask, + abort, + send_from_directory, + stream_template, +) + + +def get_project(project: str) -> dict[str, Any]: + with open(f"./projects/{project}.json", "r") as p: + return json.loads(p.read()) + + +def get_run(project: str, run: int) -> str: + with open(f"./stdout/public/{project}/{run}", "r") as r: + return r.read() + + +def stream_run(project: str, run: int): + while True: + with open(f"./stdout/public/{project}/{run}", "r") as r: + txt = r.read() + yield txt + if "--MSCI_EXIT_" in txt: + break + time.sleep(0.5) + + +def _projects(q: queue.Queue[tuple[str, dict[str, Any]] | None]): + if os.path.isdir("./projects"): + _projects = os.listdir("./projects") + for _p in _projects: + loaded = get_project(_p.replace(".json", "")) + if not loaded["hidden"]: + q.put((_p.replace(".json", ""), loaded)) + else: + os.mkdir("./projects") + q.put(None) + + +def projects(): + q: queue.Queue[tuple[str, dict[str, Any]] | None] = queue.Queue() + threading.Thread(target=_projects, args=(q,), daemon=True).start() + while True: + pr = q.get() + if pr is None: + break + yield pr + + +def _project_runs(project: str, q: queue.Queue[dict[str, Any] | None]): + pr_stdout = f"./stdout/public/{project}" + if os.path.isdir(pr_stdout): + _runs = sorted(os.listdir(pr_stdout), key=int) + for _r in _runs: + run_str = get_run(project, int(_r)) + run_date = re.search(r"--MSCI_DATE\((.*?)\)--", run_str) + run_status = ( + True + if "--MSCI_EXIT_SUCCESS--" in run_str + else False if "--MSCI_EXIT_FAILURE--" in run_str else None + ) + run_data = { + "number": int(_r), + "date": run_date.group(1) if run_date else None, + "status": run_status, + } + q.put(run_data) + q.put(None) + + +def project_runs(project: str): + q: queue.Queue[dict[str, Any] | None] = queue.Queue() + threading.Thread( + target=_project_runs, + args=( + project, + q, + ), + daemon=True, + ).start() + while True: + pr = q.get() + if pr is None: + break + yield pr + + +def project_exists(name: str): + return ( + os.path.isfile(f"./projects/{name}.json") + and get_project(name)["hidden"] == False + ) + + +def run_exists(project: str, run: int): + if project_exists(project): + if not os.path.isfile(f"./stdout/public/{project}/{run}"): + return False + return True + return False + + +app = Flask(__name__) + + +@app.route("/favicon.ico") +def favicon(): + return send_from_directory( + app.root_path, + "favicon.ico", + mimetype="image/vnd.microsoft.icon", + ) + + +@app.route("/", methods=["GET"]) +def home(): + return stream_template("home.html", projects=projects()) + + +@app.route("/<string:project>", methods=["GET"]) +def project(project: str): + if project_exists(project): + return stream_template( + "project.html", + project=get_project(project), + project_path=project, + runlist=project_runs(project), + ) + abort(404) + + +@app.route("/<string:project>/<int:run>", methods=["GET"]) +def run(project: str, run: int): + if run_exists(project, run): + return stream_template( + "run.html", + project=get_project(project), + project_path=project, + run=stream_run(project, run), + run_number=run, + ) + abort(404) |
