aboutsummaryrefslogtreecommitdiff
path: root/web.py
diff options
context:
space:
mode:
authortoufic ar <contact@toufy.me>2026-05-11 19:43:18 +0300
committertoufic ar <contact@toufy.me>2026-05-11 19:43:18 +0300
commit1817d117d020318ba5fcd281708015eaad3f092c (patch)
treed921291bbe95524eff1ff628591426e25541c0dc /web.py
parentebbdc72f0a4c9bcf38526520a7728221f05eaedb (diff)
downloadmakeshiftci-1817d117d020318ba5fcd281708015eaad3f092c.tar.gz
makeshiftci-1817d117d020318ba5fcd281708015eaad3f092c.zip
initial web UI
Diffstat (limited to 'web.py')
-rw-r--r--web.py151
1 files changed, 151 insertions, 0 deletions
diff --git a/web.py b/web.py
new file mode 100644
index 0000000..0bbebb7
--- /dev/null
+++ b/web.py
@@ -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)