Add bandwidth statistics tab with live chart
- Add new Statistics tab between Downloads and Settings - Implement real-time bandwidth chart using Canvas (60s history) - Add session overview with 8 stats cards (speed, downloaded, files, packages, etc.) - Add provider statistics with progress bars - Add getSessionStats IPC endpoint - Support dark/light theme in chart rendering
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
AppSettings,
|
||||
DuplicatePolicy,
|
||||
ParsedPackageInput,
|
||||
SessionStats,
|
||||
StartConflictEntry,
|
||||
StartConflictResolutionResult,
|
||||
UiSnapshot,
|
||||
@@ -225,6 +226,10 @@ export class AppController {
|
||||
return this.manager.importQueue(json);
|
||||
}
|
||||
|
||||
public getSessionStats(): SessionStats {
|
||||
return this.manager.getSessionStats();
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
abortActiveUpdateDownload();
|
||||
this.manager.prepareForShutdown();
|
||||
|
||||
@@ -4737,4 +4737,79 @@ export class DownloadManager extends EventEmitter {
|
||||
this.persistNow();
|
||||
this.emitState();
|
||||
}
|
||||
|
||||
public getSessionStats(): import("../shared/types").SessionStats {
|
||||
const now = nowMs();
|
||||
this.pruneSpeedEvents(now);
|
||||
|
||||
const bandwidthSamples: import("../shared/types").BandwidthSample[] = [];
|
||||
for (let i = this.speedEventsHead; i < this.speedEvents.length; i += 1) {
|
||||
const event = this.speedEvents[i];
|
||||
if (event) {
|
||||
bandwidthSamples.push({
|
||||
timestamp: event.at,
|
||||
speedBps: event.bytes * 3
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const paused = this.session.running && this.session.paused;
|
||||
const currentSpeedBps = paused ? 0 : this.speedBytesLastWindow / 3;
|
||||
|
||||
let totalBytes = 0;
|
||||
let maxSpeed = 0;
|
||||
for (let i = this.speedEventsHead; i < this.speedEvents.length; i += 1) {
|
||||
const event = this.speedEvents[i];
|
||||
if (event) {
|
||||
totalBytes += event.bytes;
|
||||
const speed = event.bytes * 3;
|
||||
if (speed > maxSpeed) {
|
||||
maxSpeed = speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sessionDurationSeconds = this.session.runStartedAt > 0
|
||||
? Math.max(0, Math.floor((now - this.session.runStartedAt) / 1000))
|
||||
: 0;
|
||||
|
||||
const averageSpeedBps = sessionDurationSeconds > 0
|
||||
? Math.floor(this.session.totalDownloadedBytes / sessionDurationSeconds)
|
||||
: 0;
|
||||
|
||||
let totalDownloads = 0;
|
||||
let completedDownloads = 0;
|
||||
let failedDownloads = 0;
|
||||
let activeDownloads = 0;
|
||||
let queuedDownloads = 0;
|
||||
|
||||
for (const item of Object.values(this.session.items)) {
|
||||
totalDownloads += 1;
|
||||
if (item.status === "completed") {
|
||||
completedDownloads += 1;
|
||||
} else if (item.status === "failed") {
|
||||
failedDownloads += 1;
|
||||
} else if (item.status === "downloading" || item.status === "validating") {
|
||||
activeDownloads += 1;
|
||||
} else if (item.status === "queued" || item.status === "reconnect_wait") {
|
||||
queuedDownloads += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bandwidth: {
|
||||
samples: bandwidthSamples.slice(-120),
|
||||
currentSpeedBps: Math.floor(currentSpeedBps),
|
||||
averageSpeedBps,
|
||||
maxSpeedBps: Math.floor(maxSpeed),
|
||||
totalBytesSession: this.session.totalDownloadedBytes,
|
||||
sessionDurationSeconds
|
||||
},
|
||||
totalDownloads,
|
||||
completedDownloads,
|
||||
failedDownloads,
|
||||
activeDownloads,
|
||||
queuedDownloads
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,6 +343,7 @@ function registerIpcHandlers(): void {
|
||||
const result = mainWindow ? await dialog.showOpenDialog(mainWindow, options) : await dialog.showOpenDialog(options);
|
||||
return result.canceled ? [] : result.filePaths;
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.GET_SESSION_STATS, () => controller.getSessionStats());
|
||||
|
||||
controller.onState = (snapshot) => {
|
||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||
|
||||
Reference in New Issue
Block a user