diff --git a/webgui/web/assets/js/state.js b/webgui/web/assets/js/state.js index 26aaa86..084022b 100644 --- a/webgui/web/assets/js/state.js +++ b/webgui/web/assets/js/state.js @@ -80,6 +80,12 @@ export function getJobMeta(jobid) { return state.jobMeta.get(Number(jobid)); } +export function listJobMeta() { + return Array.from(state.jobMeta.entries()) + .map(([jobid, meta]) => ({ jobid: Number(jobid), ...meta })) + .sort((a, b) => b.jobid - a.jobid); +} + export function forgetJob(jobid) { state.jobMeta.delete(Number(jobid)); saveJobMeta(); diff --git a/webgui/web/assets/js/views/jobs.js b/webgui/web/assets/js/views/jobs.js index f4423b0..487228a 100644 --- a/webgui/web/assets/js/views/jobs.js +++ b/webgui/web/assets/js/views/jobs.js @@ -7,6 +7,8 @@ import { toast, rememberJob, getJobMeta, + listJobMeta, + forgetJob, formatBytes, formatSpeed, formatDuration, @@ -14,6 +16,7 @@ import { import { t } from "../i18n.js"; let pollTimer = null; +const WEBGUI_JOB_GROUP_PREFIX = "webgui/transfer"; export async function renderJobs() { const app = document.getElementById("app"); @@ -30,8 +33,12 @@ export async function renderJobs() { `; - await refreshJobs(); - startPolling(); + const hasRunning = await refreshJobs(); + if (hasRunning) { + startPolling(); + } else { + stopPolling(); + } } export async function renderNewJob() { @@ -114,7 +121,11 @@ export async function renderNewJob() { const src = srcPath ? `${srcRemote}${srcPath}` : srcRemote; const dst = dstPath ? `${dstRemote}${dstPath}` : dstRemote; try { - const body = { srcFs: src, dstFs: dst }; + const body = { + srcFs: src, + dstFs: dst, + _group: `${WEBGUI_JOB_GROUP_PREFIX}/${action}`, + }; if (action === "move") body.deleteEmptySrcDirs = true; const res = await postAsync(`sync/${action}`, body); const jobid = res && res.jobid; @@ -131,18 +142,10 @@ export async function renderNewJob() { async function refreshJobs() { const card = document.getElementById("jobs-card"); - if (!card) return; + if (!card) return false; - let jobIds = []; - try { - const list = await post("job/list"); - const running = (list && list.jobids) || []; - const finished = (list && list.finishedIds) || []; - jobIds = [...new Set([...running, ...finished])]; - } catch (e) { - card.innerHTML = `
${escapeHtml(e.message)}
${t("jobs.empty_body").replace("New Job", `${t("jobs.new_btn")}`)}
`; - return; + return false; } const statuses = await Promise.all( jobIds.map((id) => - post("job/status", { jobid: id }).catch((e) => ({ - jobid: id, - error: e.message, - finished: true, - })), + post("job/status", { jobid: id }).catch((e) => { + if (e.message.includes("job not found")) { + forgetJob(id); + return null; + } + return { + jobid: id, + error: e.message, + finished: true, + }; + }), ), ); - card.innerHTML = renderJobTable(statuses); + const tracked = statuses.filter(Boolean); + if (tracked.length === 0) { + card.innerHTML = ` +${t("jobs.empty_body").replace("New Job", `${t("jobs.new_btn")}`)}
+