fix: 显示任务传输状态

This commit is contained in:
2026-06-20 12:50:43 +08:00
parent 0e3dc9a96f
commit c2289f889e
9 changed files with 130 additions and 33 deletions
+6 -6
View File
@@ -2,15 +2,15 @@
// the top-nav active state in sync, and re-renders chrome strings when
// the locale changes.
import { onRoute } from "./state.js?v=persist-all-jobs-1";
import { t, currentLocale, setLocale, onLocale } from "./i18n.js?v=persist-all-jobs-1";
import { renderRemotes } from "./views/remotes.js?v=persist-all-jobs-1";
import { renderBrowse } from "./views/browser.js?v=persist-all-jobs-1";
import { renderJobs, renderNewJob, stopJobPolling } from "./views/jobs.js?v=persist-all-jobs-1";
import { onRoute } from "./state.js?v=job-stats-1";
import { t, currentLocale, setLocale, onLocale } from "./i18n.js?v=job-stats-1";
import { renderRemotes } from "./views/remotes.js?v=job-stats-1";
import { renderBrowse } from "./views/browser.js?v=job-stats-1";
import { renderJobs, renderNewJob, stopJobPolling } from "./views/jobs.js?v=job-stats-1";
import {
renderConfigureNew,
renderConfigureEdit,
} from "./views/configure.js?v=persist-all-jobs-1";
} from "./views/configure.js?v=job-stats-1";
const views = {
remotes: renderRemotes,
+10
View File
@@ -238,6 +238,11 @@ const STRINGS = {
"jobs.detail_schedule": "Schedule",
"jobs.detail_group": "Group",
"jobs.detail_execute_id": "Execution",
"jobs.detail_bytes": "Bytes",
"jobs.detail_speed": "Speed",
"jobs.detail_eta": "ETA",
"jobs.detail_files": "Files",
"jobs.detail_errors": "Errors",
"jobs.detail_output": "Output",
"jobs.detail_none": "None",
"jobs.col.id": "#",
@@ -463,6 +468,11 @@ const STRINGS = {
"jobs.detail_schedule": "固定计划",
"jobs.detail_group": "分组",
"jobs.detail_execute_id": "执行实例",
"jobs.detail_bytes": "字节",
"jobs.detail_speed": "速度",
"jobs.detail_eta": "预计剩余",
"jobs.detail_files": "文件",
"jobs.detail_errors": "错误",
"jobs.detail_output": "输出",
"jobs.detail_none": "无",
"jobs.col.id": "#",
+3 -3
View File
@@ -1,8 +1,8 @@
// views/browser.js — file/folder listing with breadcrumbs, mkdir, upload, delete, rename.
import { post, uploadFile, downloadURL } from "../rc.js?v=persist-all-jobs-1";
import { toast, formatBytes, formatTime } from "../state.js?v=persist-all-jobs-1";
import { t } from "../i18n.js?v=persist-all-jobs-1";
import { post, uploadFile, downloadURL } from "../rc.js?v=job-stats-1";
import { toast, formatBytes, formatTime } from "../state.js?v=job-stats-1";
import { t } from "../i18n.js?v=job-stats-1";
export async function renderBrowse({ remote, path }) {
const app = document.getElementById("app");
+3 -3
View File
@@ -9,9 +9,9 @@
// OAuth backends (option named "token" with IsPassword) get a banner
// and disabled submit — user must run `rclone config` in a terminal.
import { post } from "../rc.js?v=persist-all-jobs-1";
import { getState, setState, toast } from "../state.js?v=persist-all-jobs-1";
import { t } from "../i18n.js?v=persist-all-jobs-1";
import { post } from "../rc.js?v=job-stats-1";
import { getState, setState, toast } from "../state.js?v=job-stats-1";
import { t } from "../i18n.js?v=job-stats-1";
// --- Route entrypoints ---
+67 -11
View File
@@ -1,13 +1,13 @@
// views/jobs.js — submit sync/copy/move jobs and manage recurring transfers.
import { post } from "../rc.js?v=persist-all-jobs-1";
import { post } from "../rc.js?v=job-stats-1";
import {
toast,
formatBytes,
formatSpeed,
formatDuration,
} from "../state.js?v=persist-all-jobs-1";
import { t } from "../i18n.js?v=persist-all-jobs-1";
} from "../state.js?v=job-stats-1";
import { t } from "../i18n.js?v=job-stats-1";
import {
createOneTimeJob,
createRecurringJob,
@@ -20,7 +20,7 @@ import {
runRecurringJobNow,
stopOneTimeJob,
stopRecurringJob,
} from "../jobs_api.js?v=persist-all-jobs-1";
} from "../jobs_api.js?v=job-stats-1";
let pollTimer = null;
const LOCAL_FS_VALUE = "__local__";
@@ -566,11 +566,13 @@ function renderJobRows(s) {
function renderJobRow(job) {
const snapshot = job.statusSnapshot || {};
const p = snapshot.progress || {};
const stats = transferStats(snapshot);
const id = job.id;
const key = jobKey(job);
const running = !!job.running;
const jobid = job.currentJobid || job.lastJobid;
const displayedSpeed = stats.speed || averageSpeed(stats);
const displayedEta = running ? stats.eta : 0;
const badge = renderStatusBadge(job.status);
const jobCell = `
@@ -590,12 +592,12 @@ function renderJobRow(job) {
</div>
`;
const pct = p && p.totalBytes > 0 ? Math.min(100, (p.bytes / p.totalBytes) * 100) : 0;
const pct = stats.totalBytes > 0 ? Math.min(100, (stats.bytes / stats.totalBytes) * 100) : 0;
const progress = `
<div style="display:flex;flex-direction:column;gap:4px;min-width:120px">
<div class="progress"><span style="width:${pct.toFixed(1)}%"></span></div>
<span class="col-mono" style="font-size:11px;color:var(--color-muted)">
${formatBytes(p.bytes)} / ${formatBytes(p.totalBytes)}
${formatBytes(stats.bytes)} / ${formatBytes(stats.totalBytes)}
</span>
</div>
`;
@@ -619,10 +621,10 @@ function renderJobRow(job) {
<td>${badge}</td>
<td>${scheduleCell}</td>
<td class="col-num">${progress}</td>
<td class="col-num col-mono">${running ? escapeHtml(formatSpeed(p.speed || 0)) : "—"}</td>
<td class="col-num col-mono">${running ? escapeHtml(formatDuration(p.eta || 0)) : "—"}</td>
<td class="col-num col-mono">${p.transfers ?? 0} / ${p.totalTransfers ?? 0}</td>
<td class="col-num col-mono">${(p.errors && p.errors.length) || 0}</td>
<td class="col-num col-mono">${escapeHtml(formatSpeed(displayedSpeed))}</td>
<td class="col-num col-mono">${escapeHtml(formatDuration(displayedEta))}</td>
<td class="col-num col-mono">${escapeHtml(formatTransferCount(stats))}</td>
<td class="col-num col-mono">${escapeHtml(String(stats.errors || 0))}</td>
<td class="col-num">
<div class="job-actions">
${detailBtn}
@@ -655,6 +657,7 @@ function renderStatusBadge(status) {
function renderJobDetailsRow(job) {
const snapshot = job.statusSnapshot || {};
const stats = transferStats(snapshot);
const jobid = job.currentJobid || job.lastJobid || "";
const details = [
[job.kind === "once" ? t("jobs.detail_record_id") : t("jobs.detail_schedule_id"), displayJobId(job)],
@@ -668,6 +671,11 @@ function renderJobDetailsRow(job) {
[t("jobs.detail_schedule"), formatSchedule(job)],
[t("jobs.detail_group"), snapshot.group || ""],
[t("jobs.detail_execute_id"), snapshot.executeId || ""],
[t("jobs.detail_bytes"), `${formatBytes(stats.bytes)} / ${formatBytes(stats.totalBytes)}`],
[t("jobs.detail_speed"), formatSpeed(stats.speed || averageSpeed(stats))],
[t("jobs.detail_eta"), formatDuration(job.running ? stats.eta : 0)],
[t("jobs.detail_files"), formatTransferCount(stats)],
[t("jobs.detail_errors"), String(stats.errors || 0)],
];
const output = snapshot && Object.keys(snapshot).length > 0
? JSON.stringify(snapshot, null, 2)
@@ -698,6 +706,54 @@ function renderJobDetailsRow(job) {
`;
}
function transferStats(snapshot) {
const stats = snapshot.stats || snapshot.progress || snapshot || {};
return {
bytes: numberOrZero(stats.bytes),
totalBytes: numberOrZero(stats.totalBytes),
speed: numberOrZero(stats.speed),
eta: numberOrNull(stats.eta),
transfers: numberOrZero(stats.transfers),
totalTransfers: numberOrZero(stats.totalTransfers),
checks: numberOrZero(stats.checks),
totalChecks: numberOrZero(stats.totalChecks),
transferTime: numberOrZero(stats.transferTime),
errors: normalizeErrors(stats.errors),
};
}
function averageSpeed(stats) {
if (stats.bytes > 0 && stats.transferTime > 0) {
return stats.bytes / stats.transferTime;
}
return 0;
}
function formatTransferCount(stats) {
if (stats.totalTransfers > 0) {
return `${stats.transfers} / ${stats.totalTransfers}`;
}
if (stats.totalChecks > 0) {
return `${stats.checks} / ${stats.totalChecks}`;
}
return String(stats.transfers || stats.checks || 0);
}
function normalizeErrors(errors) {
if (Array.isArray(errors)) return errors.length;
return numberOrZero(errors);
}
function numberOrZero(value) {
const n = Number(value);
return Number.isFinite(n) ? n : 0;
}
function numberOrNull(value) {
const n = Number(value);
return Number.isFinite(n) ? n : null;
}
function detailStatus(job) {
const status = job.status || "scheduled";
const key = `jobs.status.${status}`;
+3 -3
View File
@@ -1,8 +1,8 @@
// views/remotes.js — connector-tile grid of configured remotes with CRUD.
import { post } from "../rc.js?v=persist-all-jobs-1";
import { getState, setState, toast } from "../state.js?v=persist-all-jobs-1";
import { t } from "../i18n.js?v=persist-all-jobs-1";
import { post } from "../rc.js?v=job-stats-1";
import { getState, setState, toast } from "../state.js?v=job-stats-1";
import { t } from "../i18n.js?v=job-stats-1";
export async function renderRemotes() {
const app = document.getElementById("app");
+2 -2
View File
@@ -7,7 +7,7 @@
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg">
<link rel="stylesheet" href="/assets/styles/tokens.css">
<link rel="stylesheet" href="/assets/styles/base.css">
<link rel="stylesheet" href="/assets/styles/components.css?v=persist-all-jobs-1">
<link rel="stylesheet" href="/assets/styles/components.css?v=job-stats-1">
</head>
<body>
<header class="top-nav">
@@ -80,6 +80,6 @@
<div id="toast-stack" class="toast-stack"></div>
<div id="modal-root"></div>
<script type="module" src="/assets/js/app.js?v=persist-all-jobs-1"></script>
<script type="module" src="/assets/js/app.js?v=job-stats-1"></script>
</body>
</html>