Naut-Plugin-WebUI/public/js/format.js
ookami125 d1b90cfdcb webui: fix relative timestamps (seconds, not milliseconds)
f.date/f.ago assumed JS milliseconds, but the backend emits Unix epoch
seconds everywhere (created_at, feed lastUpdate, rule lastMatch, …), so
every real timestamp rendered as ~20607 days ago (now - ~1.78e9 ms).
Treat the formatter input as seconds. Search results show the indexer's
pubDate string directly rather than mis-parsing it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 01:25:43 -04:00

88 lines
3.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Display formatters — units, durations, dates, states.
export function bytes(n, dp = 1) {
if (n == null || isNaN(n)) return '';
if (n === 0) return '0 B';
const u = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
const i = Math.min(u.length - 1, Math.floor(Math.log(n) / Math.log(1024)));
return `${(n / 1024 ** i).toFixed(i === 0 ? 0 : dp)} ${u[i]}`;
}
export function rate(n) {
if (!n) return '';
return `${bytes(n)}/s`;
}
export function eta(sec) {
if (sec == null || sec >= 8640000 || sec < 0) return '∞';
if (sec < 1) return '0s';
const d = Math.floor(sec / 86400);
const h = Math.floor((sec % 86400) / 3600);
const m = Math.floor((sec % 3600) / 60);
const s = Math.floor(sec % 60);
if (d) return `${d}d ${h}h`;
if (h) return `${h}h ${m}m`;
if (m) return `${m}m ${s}s`;
return `${s}s`;
}
export function duration(sec) {
if (sec == null || sec < 0) return '';
const d = Math.floor(sec / 86400);
const h = Math.floor((sec % 86400) / 3600);
const m = Math.floor((sec % 3600) / 60);
if (d) return `${d}d ${h}h ${m}m`;
if (h) return `${h}h ${m}m`;
return `${m}m`;
}
export function pct(p) { return `${(p * 100).toFixed(1)}%`; }
export function ratio(r) {
if (r == null) return '';
if (r >= 9999) return '∞';
return r.toFixed(2);
}
// Timestamps from the backend are Unix epoch SECONDS; 0/negative means "unset".
export function date(sec) {
if (!sec || sec < 0) return '';
const d = new Date(sec * 1000);
return d.toLocaleString(undefined, { year: '2-digit', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' });
}
export function ago(sec) {
if (!sec || sec < 0) return '';
const s = Date.now() / 1000 - sec;
if (s < 60) return 'just now';
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
return `${Math.floor(s / 86400)}d ago`;
}
const STATE_LABEL = {
downloading: 'Downloading', forcedDL: '[F] Downloading', metaDL: 'Fetching metadata',
stalledDL: 'Stalled (DL)', uploading: 'Seeding', forcedUP: '[F] Seeding',
stalledUP: 'Seeding (idle)', pausedDL: 'Paused', pausedUP: 'Completed',
checkingDL: 'Checking', checkingUP: 'Checking', moving: 'Moving',
queuedDL: 'Queued', queuedUP: 'Queued', error: 'Error', missingFiles: 'Missing files',
};
export function stateLabel(s) { return STATE_LABEL[s] || s; }
const STATE_COLOR = {
downloading: 'var(--dl)', forcedDL: 'var(--dl)', metaDL: 'var(--dl)',
uploading: 'var(--up)', forcedUP: 'var(--up)',
stalledDL: 'var(--txt-dim)', stalledUP: 'var(--txt-dim)',
pausedDL: 'var(--pause)', pausedUP: 'var(--pause)',
checkingDL: 'var(--check)', checkingUP: 'var(--check)', moving: 'var(--check)',
queuedDL: 'var(--queue)', queuedUP: 'var(--queue)',
error: 'var(--err)', missingFiles: 'var(--err)',
};
export function stateColor(s) { return STATE_COLOR[s] || 'var(--txt-dim)'; }
const PRIO_LABEL = { 0: 'Do not download', 1: 'Normal', 6: 'High', 7: 'Maximum' };
export function filePrio(p) { return PRIO_LABEL[p] ?? 'Normal'; }
export function esc(s) {
return String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
}