102 lines
4.2 KiB
Python
102 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.responses import HTMLResponse
|
|
|
|
from ..layout.page import page
|
|
|
|
|
|
def register(app: FastAPI) -> None:
|
|
@app.get("/ui/tasks")
|
|
async def ui_tasks() -> HTMLResponse:
|
|
body = """
|
|
<h1>Tasks</h1>
|
|
<div class="card">
|
|
<div class="row">
|
|
<button class="btn" id="refresh">Refresh</button>
|
|
<a class="btn" href="/ui/tasks/new" style="display:inline-block">New Task</a>
|
|
</div>
|
|
<div style="height:10px"></div>
|
|
<div id="out" class="muted">Loading...</div>
|
|
</div>
|
|
""".strip()
|
|
script = """
|
|
const out = document.getElementById("out");
|
|
async function refresh() {
|
|
out.textContent = "Loading...";
|
|
try {
|
|
const q = await apiJson("/api/v2/queue");
|
|
const completedLimit = 25;
|
|
const completedOffset = Number(localStorage.getItem("mvp_completed_offset") || "0") || 0;
|
|
const done = await apiJson("/api/v2/tasks?limit=" + completedLimit + "&offset=" + completedOffset + "&states=SUCCEEDED,FAILED,CANCELED");
|
|
|
|
function pill(state) {
|
|
const s = String(state || "");
|
|
if (s === "SUCCEEDED") return `<span class="pill ok">${s}</span>`;
|
|
if (s === "FAILED") return `<span class="pill bad">${s}</span>`;
|
|
if (s === "CANCELED") return `<span class="pill">${s}</span>`;
|
|
if (s === "RUNNING") return `<span class="pill ok">${s}</span>`;
|
|
if (s === "QUEUED" || s === "PENDING_RESOURCES" || s === "SUBMITTING" || s === "SUBMITTED") return `<span class="pill">${s}</span>`;
|
|
return `<span class="pill">${s}</span>`;
|
|
}
|
|
function row(t) {
|
|
const id = t.task_id;
|
|
return `<tr>
|
|
<td><a href="/ui/tasks/${id}">${id}</a></td>
|
|
<td>${t.workload}</td>
|
|
<td>${pill(t.state)}</td>
|
|
<td>${t.nnodes} x ${t.n_gpus_per_node} GPU</td>
|
|
<td>${t.updated_at || ""}</td>
|
|
</tr>`;
|
|
}
|
|
|
|
const running = (q.running || []).map(row).join("");
|
|
const pending = (q.pending || []).map(row).join("");
|
|
const doneRows = (done.tasks || []).map(row).join("");
|
|
const pageNo = Math.floor(completedOffset / completedLimit) + 1;
|
|
const prevDisabled = completedOffset <= 0;
|
|
const nextDisabled = !done.has_more;
|
|
out.innerHTML = `
|
|
<div class="muted">Tip: configure token in <a href="/ui/login">Login</a>.</div>
|
|
<div style="height:10px"></div>
|
|
<h3>Running</h3>
|
|
<table><thead><tr><th>Task</th><th>Workload</th><th>State</th><th>Resources</th><th>Updated</th></tr></thead><tbody>${running || "<tr><td colspan=5 class=muted>(none)</td></tr>"}</tbody></table>
|
|
<div style="height:12px"></div>
|
|
<h3>Pending</h3>
|
|
<table><thead><tr><th>Task</th><th>Workload</th><th>State</th><th>Resources</th><th>Updated</th></tr></thead><tbody>${pending || "<tr><td colspan=5 class=muted>(none)</td></tr>"}</tbody></table>
|
|
<div style="height:12px"></div>
|
|
<h3>Completed</h3>
|
|
<div class="row" style="justify-content: space-between; margin-bottom: 8px;">
|
|
<div class="muted">Page ${pageNo}</div>
|
|
<div class="row">
|
|
<button class="btn" id="done-prev" ${prevDisabled ? "disabled" : ""}>Prev</button>
|
|
<button class="btn" id="done-next" ${nextDisabled ? "disabled" : ""}>Next</button>
|
|
</div>
|
|
</div>
|
|
<table><thead><tr><th>Task</th><th>Workload</th><th>State</th><th>Resources</th><th>Updated</th></tr></thead><tbody>${doneRows || "<tr><td colspan=5 class=muted>(none)</td></tr>"}</tbody></table>
|
|
`;
|
|
|
|
const prevBtn = document.getElementById("done-prev");
|
|
const nextBtn = document.getElementById("done-next");
|
|
if (prevBtn) prevBtn.onclick = () => {
|
|
const cur = Number(localStorage.getItem("mvp_completed_offset") || "0") || 0;
|
|
const next = Math.max(0, cur - completedLimit);
|
|
localStorage.setItem("mvp_completed_offset", String(next));
|
|
refresh();
|
|
};
|
|
if (nextBtn) nextBtn.onclick = () => {
|
|
const cur = Number(localStorage.getItem("mvp_completed_offset") || "0") || 0;
|
|
const next = cur + completedLimit;
|
|
localStorage.setItem("mvp_completed_offset", String(next));
|
|
refresh();
|
|
};
|
|
} catch (e) {
|
|
out.textContent = "Error: " + (e.status || "") + "\\n" + (e.body || String(e));
|
|
}
|
|
}
|
|
document.getElementById("refresh").onclick = refresh;
|
|
refresh();
|
|
""".strip()
|
|
return HTMLResponse(content=page("Tasks", "tasks", body, script))
|
|
|