diff --git a/web/css/resolver-main.css b/web/css/resolver-main.css
index f321e19..d63b290 100644
--- a/web/css/resolver-main.css
+++ b/web/css/resolver-main.css
@@ -2417,10 +2417,21 @@
display: flex;
justify-content: space-between;
align-items: center;
+ gap: 8px;
margin-top: 6px;
font-size: 11px;
color: var(--mr-text-muted);
}
+.mr-progress-text > span:first-child {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.mr-progress-text > span:last-child {
+ flex: 0 0 auto;
+ text-align: right;
+ white-space: nowrap;
+}
/* Scrollbar */
.mr-scrollable {
diff --git a/web/resolver/actions/queue_methods.js b/web/resolver/actions/queue_methods.js
index f50d53b..2be59e1 100644
--- a/web/resolver/actions/queue_methods.js
+++ b/web/resolver/actions/queue_methods.js
@@ -762,7 +762,7 @@ export const queueMethods = {
const nodeLabel = info.missing?.subgraph_name || info.missing?.node_type || (info.missing?.subgraph_id ? 'Subgraph' : 'Node');
const downloaded = this.formatBytes(progress.downloaded || 0);
const total = progress.total_size ? this.formatBytes(progress.total_size) : '';
- const speed = progress.speed ? `${this.formatBytes(progress.speed)}/s` : '';
+ const progressMeta = this.formatDownloadProgressMeta?.(progress) || '';
const status = progress.status || info.lastStatus || 'starting';
const statusLabel = status === 'downloading'
? `${Math.round(percent)}%`
@@ -795,7 +795,7 @@ export const queueMethods = {
}
html += `
`;
html += `
`;
- html += `
${this.escapeHtml(sizeText)}${this.escapeHtml(speed)}
`;
+ html += `
${this.escapeHtml(sizeText)}${this.escapeHtml(progressMeta)}
`;
html += `
`;
html += ``;
html += ``;
diff --git a/web/resolver/actions/resolve_download_methods.js b/web/resolver/actions/resolve_download_methods.js
index b33b81e..494c31c 100644
--- a/web/resolver/actions/resolve_download_methods.js
+++ b/web/resolver/actions/resolve_download_methods.js
@@ -779,14 +779,14 @@ export const resolveDownloadMethods = {
const percent = Math.max(0, Math.min(100, Number(progress.progress) || 0));
const downloaded = this.formatBytes(progress.downloaded || 0);
const total = this.formatBytes(progress.total_size || 0);
- const speed = progress.speed ? this.formatBytes(progress.speed) + '/s' : '';
+ const progressMeta = this.formatDownloadProgressMeta(progress);
const leftText = progress.total_size
? `${downloaded} / ${total} (${percent}%)`
: 'Connecting...';
progressDiv.innerHTML = this.renderProgressWithAction({
percent,
leftText,
- rightText: speed,
+ rightText: progressMeta,
actionClass: canCancel ? 'cancel-download-btn mr-btn mr-btn-danger mr-btn-sm' : '',
actionText: canCancel ? 'Cancel' : '',
actionDataAttr: canCancel ? `data-download-id="${downloadId}"` : '',
@@ -2391,10 +2391,53 @@ export const resolveDownloadMethods = {
* Format bytes to human readable string
*/
formatBytes(bytes) {
- if (bytes === 0) return '0 B';
+ const value = Number(bytes) || 0;
+ if (!Number.isFinite(value) || value <= 0) return '0 B';
const k = 1024;
- const sizes = ['B', 'KB', 'MB', 'GB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.min(sizes.length - 1, Math.floor(Math.log(value) / Math.log(k)));
+ return parseFloat((value / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
+ },
+
+ formatDuration(seconds) {
+ const totalSeconds = Math.max(0, Math.ceil(Number(seconds) || 0));
+ if (!Number.isFinite(totalSeconds) || totalSeconds <= 0) return '';
+
+ if (totalSeconds < 60) return `${totalSeconds}s`;
+
+ const hours = Math.floor(totalSeconds / 3600);
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
+ const remainingSeconds = totalSeconds % 60;
+
+ if (hours > 0) {
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
+ }
+
+ return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
+ },
+
+ getDownloadEtaText(progress = {}) {
+ const totalSize = Number(progress.total_size) || 0;
+ const downloaded = Number(progress.downloaded) || 0;
+ const speed = Number(progress.speed) || 0;
+ if (totalSize <= 0 || downloaded <= 0 || speed <= 0 || downloaded >= totalSize) return '';
+
+ const duration = this.formatDuration((totalSize - downloaded) / speed);
+ return duration ? `ETA ${duration}` : '';
+ },
+
+ formatDownloadProgressMeta(progress = {}) {
+ const parts = [];
+ const speed = Number(progress.speed) || 0;
+ if (speed > 0) {
+ parts.push(`${this.formatBytes(speed)}/s`);
+ }
+
+ const eta = this.getDownloadEtaText(progress);
+ if (eta) {
+ parts.push(eta);
+ }
+
+ return parts.join(' | ');
}
};