feat(webui): enhanced tasks page with countdown and status dots
This commit is contained in:
parent
f0ef14099d
commit
ee5bc46cc6
@ -10,6 +10,7 @@
|
||||
let runs = $state({});
|
||||
let loading = $state(true);
|
||||
let error = $state("");
|
||||
let tick = $state(0);
|
||||
|
||||
async function load() {
|
||||
loading = true;
|
||||
@ -21,7 +22,7 @@
|
||||
jobs = (await api("/api/jobs")).jobs;
|
||||
runs = Object.fromEntries(await Promise.all(jobs.map(async (job) => [
|
||||
job.id,
|
||||
await api(`/api/jobs/${encodeURIComponent(job.id)}/runs?limit=5`).then((value) => value.runs).catch(() => [])
|
||||
await api(`/api/jobs/${encodeURIComponent(job.id)}/runs?limit=10`).then((value) => value.runs).catch(() => [])
|
||||
])));
|
||||
}
|
||||
} catch (caught) {
|
||||
@ -36,7 +37,43 @@
|
||||
load();
|
||||
}
|
||||
|
||||
onMount(load);
|
||||
function countdown(ts) {
|
||||
void tick;
|
||||
if (!ts) return "—";
|
||||
const ms = ts < 1e12 ? ts * 1000 : ts;
|
||||
const diff = ms - Date.now();
|
||||
if (diff <= 0) return "即将运行";
|
||||
const mins = Math.floor(diff / 60000);
|
||||
if (mins < 1) return "即将运行";
|
||||
if (mins < 60) return `${mins} 分钟后`;
|
||||
const hours = Math.floor(mins / 60);
|
||||
if (hours < 24) return `${hours} 小时后`;
|
||||
return `${Math.floor(hours / 24)} 天后`;
|
||||
}
|
||||
|
||||
function elapsed(createdAt) {
|
||||
void tick;
|
||||
if (!createdAt) return "";
|
||||
const ms = createdAt < 1e12 ? createdAt * 1000 : createdAt;
|
||||
const secs = Math.floor((Date.now() - ms) / 1000);
|
||||
if (secs < 0) return "";
|
||||
if (secs < 60) return `${secs}s`;
|
||||
if (secs < 3600) return `${Math.floor(secs / 60)}m ${secs % 60}s`;
|
||||
return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
|
||||
}
|
||||
|
||||
function dotColor(status) {
|
||||
if (status === "completed" || status === "success" || status === "ok") return "var(--signal)";
|
||||
if (status === "timeout") return "var(--accent)";
|
||||
if (status === "running") return "var(--info)";
|
||||
return "var(--danger)";
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
load();
|
||||
const timer = setInterval(() => { tick += 1; }, 30000);
|
||||
return () => clearInterval(timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<section class="page active content-page">
|
||||
@ -54,15 +91,60 @@
|
||||
{:else if error}<div class="empty-card error-text">{error}</div>
|
||||
{:else if tab === "background"}
|
||||
{#each tasks as task (task.id)}
|
||||
<article class="card"><div class="card-row"><div><h3>{task.prompt.slice(0, 100)}</h3><div class="meta"><span>{task.session_id}</span><span>{formatTime(task.created_at)}</span><span>{task.tool_calls_count} 次工具调用 · {task.iterations} 轮</span></div></div><StatusBadge status={task.status} /></div>{#if task.result}<div class="details"><p>{task.result}</p></div>{/if}{#if task.error}<p class="error-text">{task.error}</p>{/if}</article>
|
||||
<article class="card">
|
||||
<div class="card-row">
|
||||
<div>
|
||||
<h3>
|
||||
{#if task.status === "running"}<span class="pulse"></span>{/if}
|
||||
{task.prompt.slice(0, 100)}
|
||||
</h3>
|
||||
<div class="meta">
|
||||
<span>{task.session_id}</span>
|
||||
<span>{formatTime(task.created_at)}</span>
|
||||
{#if task.status === "running"}<span class="elapsed">{elapsed(task.created_at)}</span>{/if}
|
||||
<span>{task.tool_calls_count} 次工具调用 · {task.iterations} 轮</span>
|
||||
</div>
|
||||
</div>
|
||||
<StatusBadge status={task.status} />
|
||||
</div>
|
||||
{#if task.result}<div class="details"><p>{task.result}</p></div>{/if}
|
||||
{#if task.error}<p class="error-text">{task.error}</p>{/if}
|
||||
</article>
|
||||
{:else}<div class="empty-card">暂无后台任务</div>{/each}
|
||||
{:else}
|
||||
{#each jobs as job (job.id)}
|
||||
<article class="card">
|
||||
<div class="card-row"><div><h3>{job.name}</h3><p>{job.prompt}</p><div class="meta"><span>{job.job_kind === "monitor" ? "巡检" : "任务"} · {job.delivery_policy}</span><span>{job.channel} · {job.chat_id}</span><span>下次 {formatTime(job.next_run_at)}</span><span>上次 {formatTime(job.last_run_at)}</span></div></div><StatusBadge status={job.enabled ? (job.last_status || "enabled") : "disabled"} /></div>
|
||||
<div class="card-row">
|
||||
<div>
|
||||
<h3>{job.name}</h3>
|
||||
<p>{job.prompt}</p>
|
||||
<div class="meta">
|
||||
<span class="mono cron">{job.cron}</span>
|
||||
<span>{job.job_kind === "monitor" ? "巡检" : "任务"} · {job.delivery_policy}</span>
|
||||
<span>{job.channel} · {job.chat_id}</span>
|
||||
<span>下次 {countdown(job.next_run_at)}</span>
|
||||
<span>上次 {formatTime(job.last_run_at)}</span>
|
||||
</div>
|
||||
{#if runs[job.id]?.length}
|
||||
<div class="status-dots">
|
||||
{#each runs[job.id] as run}
|
||||
<span class="dot" style="background:{dotColor(run.status)}" title="{run.status} {formatTime(run.finished_at)}"></span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<StatusBadge status={job.enabled ? (job.last_status || "enabled") : "disabled"} />
|
||||
</div>
|
||||
{#if runs[job.id]?.length}<div class="details">{#each runs[job.id] as run}<div class="card-row run-row"><span class="meta">{formatTime(run.finished_at)} · {run.duration_ms}ms{run.result_kind ? ` · ${run.result_kind}` : ""}{run.delivery_status ? ` · ${run.delivery_status}` : ""}</span><StatusBadge status={run.status} /></div>{/each}</div>{/if}
|
||||
</article>
|
||||
{:else}<div class="empty-card">暂无定时任务</div>{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.cron { font-size: 11px; color: var(--text-soft); background: var(--code-bg); padding: 1px 5px; border-radius: 4px; border: 1px solid var(--line); }
|
||||
.status-dots { display: flex; gap: 4px; margin-top: 6px; align-items: center; }
|
||||
.dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
|
||||
.elapsed { color: var(--info); font-family: var(--font-mono); font-size: 10px; }
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user