commit ac67546779040c0b148ceee0f07c50a49dc43a19
parent e1ee312749718a9e81efcd3c0c14f89167d5ed18
Author: archiveanon <>
Date: Tue, 21 Jan 2025 07:07:27 +0000
Implement task browsing
Diffstat:
7 files changed, 176 insertions(+), 9 deletions(-)
diff --git a/src/autotako/app.py b/src/autotako/app.py
@@ -5,6 +5,7 @@ import datetime
import json
import pathlib
+import httpx
import microdot # type: ignore
import microdot.jinja # type: ignore
import mistune
@@ -42,7 +43,25 @@ def create_app():
@app.get("/")
async def index(request):
- return await microdot.jinja.Template("index.html").render_async(message="hello")
+ async with httpx.AsyncClient() as client:
+ result = await client.get(f"{config.moombox_url}/status")
+ return (
+ await microdot.jinja.Template("index.html").render_async(
+ moombox_jobs=reversed(result.json()),
+ moombox_url=config.moombox_url,
+ ),
+ {
+ "Content-Type": "text/html; charset=utf-8",
+ },
+ )
+
+ @app.get("/static/<path:path>")
+ async def send_static_file(request, path):
+ root = pathlib.Path(__file__).parent / "static"
+ computed_path = root / path
+ if not (root / path).resolve().is_relative_to(root.resolve()):
+ return "", 404
+ return microdot.send_file(str(computed_path))
@app.post("/submit")
async def process_job(request):
diff --git a/src/autotako/job_render.py b/src/autotako/job_render.py
@@ -3,6 +3,7 @@
import asyncio
import datetime
import pathlib
+import traceback
import gofile.api # type: ignore
import httpx
@@ -16,6 +17,7 @@ from .database import database_ctx
app = microdot.Microdot()
background_tasks = set()
+render_tasks: dict[str, asyncio.Task] = {}
upload_tasks: dict[pathlib.Path, asyncio.Task] = {}
@@ -136,8 +138,7 @@ async def do_webdav_upload(webdav: WebDavConfig, filepath: pathlib.Path, target:
await client.put(dest, content=fh.read())
-@app.get("/<jobid>")
-async def show_job(request, jobid):
+async def _process_job(jobid):
job = await get_moombox_job_by_id(jobid)
if not job:
return "Couldn't find matching job", 404
@@ -246,4 +247,34 @@ async def show_job(request, jobid):
)
background_tasks.add(task)
task.add_done_callback(background_tasks.discard)
+ if not readme_finalized and jobid in render_tasks:
+ # allow rerun if not finalized
+ del render_tasks[jobid]
return rendered_job
+
+
+def get_process_job_task(jobid: str) -> asyncio.Task:
+ """
+ Creates or retrieves a singleton job rendering task.
+ This is decoupled from the request callback to allow cancellations and multiple consumers.
+ """
+ if jobid in render_tasks and render_tasks[jobid].done() and render_tasks[jobid].exception():
+ del render_tasks[jobid]
+ if jobid not in render_tasks:
+ render_tasks[jobid] = asyncio.create_task(_process_job(jobid))
+ return render_tasks[jobid]
+
+
+@app.get("/<jobid>")
+async def show_job(request, jobid: str):
+ return await get_process_job_task(jobid)
+
+
+@app.post("/<jobid>")
+async def upload_job(request, jobid: str):
+ try:
+ await get_process_job_task(jobid)
+ return await microdot.jinja.Template("job/upload_success.html").render_async()
+ except torf.TorfError:
+ traceback.print_exc()
+ return await microdot.jinja.Template("job/upload_error.html").render_async()
diff --git a/src/autotako/static/style.css b/src/autotako/static/style.css
@@ -0,0 +1,67 @@
+body {
+ width: auto;
+ font-family: var(--sl-font-sans);
+}
+a {
+ color: var(--sl-color-primary-700);
+ text-decoration: none;
+ &:hover {
+ color: var(--sl-color-primary-800);
+ }
+}
+.job-table {
+ display: grid;
+ grid-template-columns: 66% auto fit-content(0);
+ gap: 0 var(--sl-spacing-medium);
+}
+.job__item {
+ display: grid;
+ grid-template-columns: subgrid;
+ grid-column: 1/4;
+ padding: var(--sl-spacing-x-small);
+
+ &:nth-child(odd) {
+ background-color: var( --sl-color-neutral-50);
+ }
+}
+.job__item--disabled {
+ .job__info {
+ filter: saturate(0%) brightness(50%);
+ }
+}
+.job__info {
+ display: flex;
+ flex-direction: column;
+}
+.job__title {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.job__author {
+ font-size: var(--sl-font-size-small);
+ color: var(--sl-color-neutral-600);
+}
+.job__description {
+ display: flex;
+ align-items: center;
+ input {
+ flex: 1;
+ }
+}
+.job__controls {
+ display: flex;
+ align-items: center;
+}
+.job__autoupload {
+ color: var(--sl-color-neutral-500);
+}
+.job__autoupload--active sl-icon-button::part(base) {
+ color: var(--sl-color-primary-500);
+}
+.job__manualupload--done sl-icon-button::part(base) {
+ color: var(--sl-color-success-600);
+}
+.job__manualupload--fail sl-icon-button::part(base) {
+ color: var(--sl-color-danger-600);
+}
diff --git a/src/autotako/templates/edit.html b/src/autotako/templates/edit.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="sl-theme-dark">
+ <head>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>autotako edit</title>
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.16.0/cdn/themes/dark.css" crossorigin="anonymous"/>
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.16.0/cdn/shoelace-autoloader.js" crossorigin="anonymous"></script>
+ <link rel="stylesheet" href="/static/style.css" type="text/css" />
+ </head>
+ <body>
+
+ </body>
+</html>
diff --git a/src/autotako/templates/index.html b/src/autotako/templates/index.html
@@ -1,11 +1,43 @@
<!DOCTYPE html>
-<html>
+<html class="sl-theme-dark">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Hello!</title>
- <style type="text/css">.body { width: auto; }</style>
+ <title>autotako control panel</title>
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.16.0/cdn/themes/dark.css" crossorigin="anonymous"/>
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.16.0/cdn/shoelace-autoloader.js" crossorigin="anonymous"></script>
+ <script src="https://unpkg.com/htmx.org@2.0.0" integrity="sha384-wS5l5IKJBvK6sPTKa2WZ1js3d947pvWXbPJ1OmWfEuxLgeHcEbjUUA5i9V5ZkpCw" crossorigin="anonymous"></script>
+ <link rel="stylesheet" href="static/style.css" type="text/css" />
</head>
<body>
- {{ message }}
+ <h2>autotako control panel = w=)b</h2>
+ <div class="job-table">
+{% for job in moombox_jobs %}
+{% set no_outputs = (not job.output_paths and job.status == "Finished") %}
+ <div class="job__item {{- ' job__item--disabled' if no_outputs }}">
+ <div class="job__info">
+ <div class="job__title">
+ <a href="{{ moombox_url }}job/{{ job.id }}">{{ job.title }}</a>
+ </div>
+ <div class="job__author">
+ {{ job.author }}
+ </div>
+ </div>
+ <div class="job__description">
+ Unarchived Karaoke
+ <sl-icon-button name="pencil-square"></sl-icon-button>
+ </div>
+ <div class="job__controls">
+ <div class="job__autoupload">
+ <sl-tooltip content="Not scheduled for auto-upload">
+ <sl-icon name="stopwatch"></sl-icon>
+ </sl-tooltip>
+ </div>
+ <div class="job__manualupload" hx-target="this">
+ <sl-icon-button hx-post="render/{{ job.id }}" hx-swap="outerHTML" name="cloud-upload" {{- ' disabled' if no_outputs }}></sl-icon-button>
+ </div>
+ </div>
+ </div>
+{% endfor %}
+ </div>
</body>
-</html>
-\ No newline at end of file
+</html>
diff --git a/src/autotako/templates/job/upload_error.html b/src/autotako/templates/job/upload_error.html
@@ -0,0 +1,3 @@
+<div class="job__manualupload job__manualupload--fail">
+ <sl-icon-button name="x-lg"></sl-icon-button>
+</div>
diff --git a/src/autotako/templates/job/upload_success.html b/src/autotako/templates/job/upload_success.html
@@ -0,0 +1,3 @@
+<div class="job__manualupload job__manualupload--done">
+ <sl-icon-button name="check2"></sl-icon-button>
+</div>