Add app runtime statistics

This commit is contained in:
Sucukdeluxe
2026-03-09 04:59:00 +01:00
parent 971f669bb6
commit 1f9a26e4b0
12 changed files with 194 additions and 8 deletions
+75 -2
View File
@@ -764,7 +764,11 @@ const emptyStats = (): DownloadStats => ({
totalFilesSession: 0,
totalFilesAllTime: 0,
totalPackages: 0,
sessionStartedAt: 0
sessionStartedAt: 0,
appSessionStartedAt: 0,
sessionRuntimeMs: 0,
totalRuntimeMs: 0,
runtimeMeasuredAt: 0
});
const emptySnapshot = (): UiSnapshot => ({
@@ -783,7 +787,7 @@ const emptySnapshot = (): UiSnapshot => ({
updateRepo: "", autoUpdateCheck: true, clipboardWatch: false, minimizeToTray: false,
theme: "dark", collapseNewPackages: true, autoSortPackagesByProgress: true, autoSkipExtracted: false, confirmDeleteSelection: true,
accountListShowDetailedDebridLinkKeys: false,
bandwidthSchedules: [], totalDownloadedAllTime: 0, totalCompletedFilesAllTime: 0,
bandwidthSchedules: [], totalDownloadedAllTime: 0, totalCompletedFilesAllTime: 0, totalRuntimeAllTimeMs: 0,
columnOrder: ["name", "size", "progress", "hoster", "account", "prio", "status", "speed"],
autoExtractWhenStopped: true,
disabledProviders: [],
@@ -928,6 +932,55 @@ function humanSize(bytes: number): string {
return `${(bytes / (1024 * 1024 * 1024 * 1024)).toFixed(3)} TB`;
}
function formatRuntimeDuration(durationMs: number): string {
const totalSeconds = Math.max(0, Math.floor((durationMs || 0) / 1000));
const minuteSeconds = 60;
const hourSeconds = 60 * minuteSeconds;
const daySeconds = 24 * hourSeconds;
const weekSeconds = 7 * daySeconds;
const monthSeconds = 30 * daySeconds;
const formatUnit = (value: number, singular: string, plural: string, padTo = 0): string => {
const normalized = Math.max(0, Math.floor(value));
const text = padTo > 0 ? String(normalized).padStart(padTo, "0") : String(normalized);
return `${text} ${normalized === 1 ? singular : plural}`;
};
if (totalSeconds < hourSeconds) {
const minutes = Math.floor(totalSeconds / minuteSeconds);
const seconds = totalSeconds % minuteSeconds;
return `${formatUnit(minutes, "Minute", "Minuten")}, ${formatUnit(seconds, "Sekunde", "Sekunden", 2)}`;
}
if (totalSeconds < daySeconds) {
const hours = Math.floor(totalSeconds / hourSeconds);
const minutes = Math.floor((totalSeconds % hourSeconds) / minuteSeconds);
return `${formatUnit(hours, "Stunde", "Stunden")}, ${formatUnit(minutes, "Minute", "Minuten", 2)}`;
}
if (totalSeconds < weekSeconds) {
const days = Math.floor(totalSeconds / daySeconds);
const hours = Math.floor((totalSeconds % daySeconds) / hourSeconds);
const minutes = Math.floor((totalSeconds % hourSeconds) / minuteSeconds);
return `${formatUnit(days, "Tag", "Tage")}, ${formatUnit(hours, "Stunde", "Stunden")}, ${formatUnit(minutes, "Minute", "Minuten")}`;
}
if (totalSeconds < monthSeconds) {
const weeks = Math.floor(totalSeconds / weekSeconds);
const days = Math.floor((totalSeconds % weekSeconds) / daySeconds);
const hours = Math.floor((totalSeconds % daySeconds) / hourSeconds);
const minutes = Math.floor((totalSeconds % hourSeconds) / minuteSeconds);
return `${formatUnit(weeks, "Woche", "Wochen")}, ${formatUnit(days, "Tag", "Tage")}, ${formatUnit(hours, "Stunde", "Stunden")}, ${formatUnit(minutes, "Minute", "Minuten")}`;
}
const months = Math.floor(totalSeconds / monthSeconds);
const weeks = Math.floor((totalSeconds % monthSeconds) / weekSeconds);
const days = Math.floor((totalSeconds % weekSeconds) / daySeconds);
const hours = Math.floor((totalSeconds % daySeconds) / hourSeconds);
const minutes = Math.floor((totalSeconds % hourSeconds) / minuteSeconds);
return `${formatUnit(months, "Monat", "Monate")}, ${formatUnit(weeks, "Woche", "Wochen")}, ${formatUnit(days, "Tag", "Tage")}, ${formatUnit(hours, "Stunde", "Stunden")}, ${formatUnit(minutes, "Minute", "Minuten")}`;
}
function formatAllDebridSourceLabel(source: AllDebridHostInfo["source"]): string {
return source === "web" ? "Web-Login" : "API-Key";
}
@@ -1317,6 +1370,7 @@ export function App(): ReactElement {
const [schedulePickerOpen, setSchedulePickerOpen] = useState(false);
const [scheduleTimeInput, setScheduleTimeInput] = useState("");
const [scheduleCountdown, setScheduleCountdown] = useState("");
const [runtimeNow, setRuntimeNow] = useState(() => Date.now());
const settingsDirtyRef = useRef(false);
const settingsDraftRevisionRef = useRef(0);
const panelDirtyRevisionRef = useRef(0);
@@ -1535,6 +1589,11 @@ export function App(): ReactElement {
return () => clearInterval(timer);
}, [snapshot.settings.scheduledStartEpochMs]);
useEffect(() => {
const timer = setInterval(() => setRuntimeNow(Date.now()), 1000);
return () => clearInterval(timer);
}, []);
const showToast = useCallback((message: string, timeoutMs = 2200): void => {
setStatusToast(message);
if (toastTimerRef.current) { clearTimeout(toastTimerRef.current); }
@@ -3716,6 +3775,12 @@ export function App(): ReactElement {
return Object.entries(stats);
}, [snapshot.session.items]);
const runtimeOffsetMs = snapshot.stats.runtimeMeasuredAt > 0
? Math.max(0, runtimeNow - snapshot.stats.runtimeMeasuredAt)
: 0;
const liveSessionRuntimeMs = Math.max(0, (snapshot.stats.sessionRuntimeMs || 0) + runtimeOffsetMs);
const liveTotalRuntimeMs = Math.max(0, (snapshot.stats.totalRuntimeMs || 0) + runtimeOffsetMs);
return (
<div
className={`app-shell${dragOver ? " drag-over" : ""}`}
@@ -4370,6 +4435,14 @@ export function App(): ReactElement {
<span className="stat-label">Heruntergeladen (Gesamt)</span>
<span className="stat-value">{humanSize(snapshot.stats.totalDownloadedAllTime)}</span>
</div>
<div className="stat-item">
<span className="stat-label">Laufzeit (Session)</span>
<span className="stat-value">{formatRuntimeDuration(liveSessionRuntimeMs)}</span>
</div>
<div className="stat-item">
<span className="stat-label">Laufzeit (Gesamt)</span>
<span className="stat-value">{formatRuntimeDuration(liveTotalRuntimeMs)}</span>
</div>
<div className="stat-item">
<span className="stat-label">Fertige Dateien (Gesamt)</span>
<span className="stat-value">{snapshot.stats.totalFilesAllTime}</span>