473 lines
23 KiB
HTML
473 lines
23 KiB
HTML
{{ define "stats" }}
|
|
<div class="container mx-auto">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h1 class="text-3xl font-bold text-primary">System Statistics</h1>
|
|
<button id="refresh-stats" class="btn btn-outline btn-sm">
|
|
<i class="bi bi-arrow-clockwise"></i>
|
|
Refresh
|
|
</button>
|
|
</div>
|
|
|
|
<div id="loading-stats" class="text-center py-12">
|
|
<div class="loading loading-spinner loading-lg text-primary"></div>
|
|
<p class="mt-4 text-base-content/70">Loading system statistics...</p>
|
|
</div>
|
|
|
|
<div id="stats-content" class="space-y-6" style="display: none;">
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-header p-6 pb-3">
|
|
<h2 class="card-title text-xl">
|
|
<i class="bi bi-cpu text-info"></i>
|
|
System Overview
|
|
</h2>
|
|
</div>
|
|
<div class="card-body p-6 pt-3">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<div class="stat">
|
|
<div class="stat-figure text-info">
|
|
<i class="bi bi-memory text-2xl"></i>
|
|
</div>
|
|
<div class="stat-title">Memory Used</div>
|
|
<div class="stat-value text-info" id="memory-used">-</div>
|
|
<div class="stat-desc" id="heap-alloc">Heap: -</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-figure text-success">
|
|
<i class="bi bi-diagram-3 text-2xl"></i>
|
|
</div>
|
|
<div class="stat-title">Goroutines</div>
|
|
<div class="stat-value text-success" id="goroutines">-</div>
|
|
<div class="stat-desc" id="gc-cycles">GC: - cycles</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-figure text-warning">
|
|
<i class="bi bi-cpu text-2xl"></i>
|
|
</div>
|
|
<div class="stat-title">CPU Cores</div>
|
|
<div class="stat-value text-warning" id="num-cpu">-</div>
|
|
<div class="stat-desc" id="arch">-</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-figure text-accent">
|
|
<i class="bi bi-info-circle text-2xl"></i>
|
|
</div>
|
|
<div class="stat-title">System</div>
|
|
<div class="stat-value text-accent" id="os">-</div>
|
|
<div class="stat-desc" id="go-version">-</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card bg-base-100 shadow-xl" id="debrid-card">
|
|
<div class="card-header p-6 pb-3">
|
|
<h2 class="card-title text-xl">
|
|
<i class="bi bi-cloud-download text-secondary"></i>
|
|
Debrid Services
|
|
</h2>
|
|
</div>
|
|
<div class="card-body p-6 pt-3" id="debrid-content">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card bg-base-100 shadow-xl" id="rclone-card">
|
|
<div class="card-header p-6 pb-3">
|
|
<div class="card-title text-xl justify-between items-center">
|
|
<h2>
|
|
<i class="bi bi-cloud-arrow-up text-primary"></i>
|
|
Rclone Statistics
|
|
</h2>
|
|
<a href="{{.URLBase}}debug/logs/rclone" class="btn btn-sm btn-outline" target="_blank">
|
|
<i class="bi bi-arrow-right"></i>
|
|
View Rclone Logs
|
|
</a>
|
|
</div>
|
|
<div class="badge" id="rclone-status">Unknown</div>
|
|
</div>
|
|
<div class="card-body p-6 pt-3" id="rclone-content">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="error-stats" class="alert alert-error" style="display: none;">
|
|
<i class="bi bi-exclamation-triangle-fill"></i>
|
|
<span id="error-message">Failed to load statistics</span>
|
|
<button id="retry-stats" class="btn btn-sm btn-outline">
|
|
<i class="bi bi-arrow-clockwise"></i>
|
|
Retry
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const loadingEl = document.getElementById('loading-stats');
|
|
const contentEl = document.getElementById('stats-content');
|
|
const errorEl = document.getElementById('error-stats');
|
|
const refreshBtn = document.getElementById('refresh-stats');
|
|
const retryBtn = document.getElementById('retry-stats');
|
|
|
|
function formatNumber(num) {
|
|
return num.toLocaleString();
|
|
}
|
|
|
|
function showError(message) {
|
|
loadingEl.style.display = 'none';
|
|
contentEl.style.display = 'none';
|
|
errorEl.style.display = 'flex';
|
|
document.getElementById('error-message').textContent = message;
|
|
}
|
|
|
|
function showContent() {
|
|
loadingEl.style.display = 'none';
|
|
errorEl.style.display = 'none';
|
|
contentEl.style.display = 'block';
|
|
}
|
|
|
|
function showLoading() {
|
|
contentEl.style.display = 'none';
|
|
errorEl.style.display = 'none';
|
|
loadingEl.style.display = 'block';
|
|
}
|
|
|
|
function updateRcloneStats(rclone) {
|
|
const rcloneCard = document.getElementById('rclone-card');
|
|
const rcloneStatus = document.getElementById('rclone-status');
|
|
const rcloneContent = document.getElementById('rclone-content');
|
|
|
|
if (!rclone || !rclone.enabled) {
|
|
rcloneStatus.textContent = 'Disabled';
|
|
rcloneStatus.className = 'badge badge-error';
|
|
rcloneContent.innerHTML = '<p class="text-base-content/70">Rclone is not enabled or configured.</p>';
|
|
return;
|
|
}
|
|
|
|
if (!rclone.server_ready) {
|
|
rcloneStatus.textContent = 'Not Ready';
|
|
rcloneStatus.className = 'badge badge-warning';
|
|
rcloneContent.innerHTML = '<p class="text-base-content/70">Rclone server is not ready.</p>';
|
|
return;
|
|
}
|
|
|
|
rcloneStatus.textContent = 'Active';
|
|
rcloneStatus.className = 'badge badge-success';
|
|
|
|
let html = '<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">';
|
|
|
|
// Version info
|
|
if (rclone.version) {
|
|
html += `
|
|
<div class="stat">
|
|
<div class="stat-title">Rclone Version</div>
|
|
<div class="stat-value text-sm">${rclone.version.version || 'Unknown'}</div>
|
|
<div class="stat-desc">${rclone.version.arch || ''} ${rclone.version.os || ''}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Core stats contain transfer information
|
|
if (rclone.core) {
|
|
const cs = rclone.core;
|
|
html += `
|
|
<div class="stat">
|
|
<div class="stat-title">Transferred</div>
|
|
<div class="stat-value text-primary">${window.decypharrUtils.formatBytes(cs.bytes || 0)}</div>
|
|
<div class="stat-desc">Speed: ${window.decypharrUtils.formatBytes(cs.speed || 0)}/s</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-title">Transfers</div>
|
|
<div class="stat-value text-info">${cs.transfers || 0}</div>
|
|
<div class="stat-desc">Errors: ${cs.errors || 0}</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-title">Checks</div>
|
|
<div class="stat-value text-secondary">${cs.checks || 0}</div>
|
|
<div class="stat-desc">Total: ${cs.totalChecks || 0}</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-title">Uptime</div>
|
|
<div class="stat-value text-accent">${window.decypharrUtils.formatDuration(cs.elapsedTime)}</div>
|
|
<div class="stat-desc">Transfer: ${window.decypharrUtils.formatDuration(cs.transferTime)}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (rclone.memory) {
|
|
const ms = rclone.memory;
|
|
html += `
|
|
<div class="stat">
|
|
<div class="stat-title">Rclone Memory</div>
|
|
<div class="stat-value text-accent">${window.decypharrUtils.formatBytes(ms.Sys || 0)}</div>
|
|
<div class="stat-desc">Heap: ${window.decypharrUtils.formatBytes(ms.TotalAlloc || 0)}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
html += '</div>';
|
|
|
|
// Add active transfers if available
|
|
if (rclone.core && rclone.core.transferring && rclone.core.transferring.length > 0) {
|
|
let transferring = rclone.core.transferring;
|
|
html += `
|
|
<div class="mt-6">
|
|
<h3 class="text-lg font-semibold mb-3">
|
|
<i class="bi bi-arrow-down-up text-primary"></i>
|
|
Active Transfers (${transferring.length})
|
|
</h3>
|
|
<div class="space-y-2 max-h-64 overflow-y-auto">
|
|
`;
|
|
|
|
transferring.forEach(transfer => {
|
|
const progress = ((transfer.bytes || 0) / (transfer.size || 1)) * 100;
|
|
html += `
|
|
<div class="card bg-base-200 compact">
|
|
<div class="card-body">
|
|
<div class="flex justify-between items-start mb-2">
|
|
<h4 class="font-medium text-sm truncate flex-1 mr-2">${transfer.name || 'Unknown'}</h4>
|
|
<span class="text-xs text-base-content/70">${window.decypharrUtils.formatBytes(transfer.speed || 0)}/s</span>
|
|
</div>
|
|
<div class="flex items-center space-x-2">
|
|
<progress class="progress progress-primary flex-1" value="${progress}" max="100"></progress>
|
|
<span class="text-xs font-mono">${progress.toFixed(1)}%</span>
|
|
</div>
|
|
<div class="flex justify-between text-xs text-base-content/60 mt-1">
|
|
<span>${window.decypharrUtils.formatBytes(transfer.bytes || 0)} / ${window.decypharrUtils.formatBytes(transfer.size || 0)}</span>
|
|
<span>ETA: ${transfer.eta ? window.decypharrUtils.formatDuration(transfer.eta) : 'Unknown'}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
html += '</div></div>';
|
|
}
|
|
|
|
// Add mount information if available
|
|
if (rclone.mounts && Object.keys(rclone.mounts).length > 0) {
|
|
html += `
|
|
<div class="mt-6">
|
|
<h3 class="text-lg font-semibold mb-3">
|
|
<i class="bi bi-hdd-stack text-primary"></i>
|
|
Mounted Services
|
|
</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
`;
|
|
|
|
Object.entries(rclone.mounts).forEach(([name, mount]) => {
|
|
const statusBadge = mount.mounted ?
|
|
'<span class="badge badge-success badge-sm">Mounted</span>' :
|
|
'<span class="badge badge-error badge-sm">Not Mounted</span>';
|
|
|
|
html += `
|
|
<div class="card bg-base-200 compact">
|
|
<div class="card-body">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h4 class="font-medium">${mount.config_name || name}</h4>
|
|
<p class="text-xs text-base-content/70">${mount.provider || 'Unknown Provider'}</p>
|
|
${mount.local_path ? `<p class="text-xs text-base-content/60 font-mono">${mount.local_path}</p>` : ''}
|
|
</div>
|
|
<div class="text-right">
|
|
${statusBadge}
|
|
${mount.mounted_at ? `<p class="text-xs text-base-content/60">Since: ${new Date(mount.mounted_at).toLocaleTimeString()}</p>` : ''}
|
|
</div>
|
|
</div>
|
|
${mount.error ? `<div class="alert alert-error alert-sm mt-2"><span class="text-xs">${mount.error}</span></div>` : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
html += '</div></div>';
|
|
}
|
|
|
|
// Add bandwidth stats if available
|
|
if (rclone.bandwidth && rclone.bandwidth.rate !== "off") {
|
|
html += `
|
|
<div class="mt-6">
|
|
<h3 class="text-lg font-semibold mb-3">
|
|
<i class="bi bi-speedometer2 text-info"></i>
|
|
Bandwidth Limits
|
|
</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
`;
|
|
|
|
html += `
|
|
<div class="stat bg-base-200 rounded-lg p-3">
|
|
<div class="stat-title text-xs">Bytes Per Seconds</div>
|
|
<div class="stat-value text-sm">${window.decypharrUtils.formatBytes(rclone.bandwidth.bytesPerSecond)}</div>
|
|
</div>
|
|
<div class="stat bg-base-200 rounded-lg p-3">
|
|
<div class="stat-title text-xs">Rate</div>
|
|
<div class="stat-value text-sm">${window.decypharrUtils.formatBytes(rclone.bandwidth.rate)}</div>
|
|
</div>
|
|
`;
|
|
|
|
html += '</div></div>';
|
|
}
|
|
|
|
rcloneContent.innerHTML = html;
|
|
}
|
|
|
|
function updateDebridStats(debrids) {
|
|
const debridContent = document.getElementById('debrid-content');
|
|
|
|
if (!debrids || debrids.length === 0) {
|
|
debridContent.innerHTML = '<p class="text-base-content/70">No debrid services configured.</p>';
|
|
return;
|
|
}
|
|
|
|
let html = '<div class="space-y-4">';
|
|
debrids.forEach(debrid => {
|
|
const profile = debrid.profile || {};
|
|
const library = debrid.library || {};
|
|
const accounts = debrid.accounts || [];
|
|
|
|
html += `
|
|
<div class="card bg-base-200">
|
|
<div class="card-body p-4">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h3 class="card-title text-lg">${profile.name || 'Unknown Service'}</h3>
|
|
<p class="text-sm text-base-content/70">${profile.username || 'No username'}</p>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-sm font-mono">${formatNumber(profile.points || 0)} points</div>
|
|
<div class="text-xs text-base-content/70">Type: ${profile.type || 'Unknown'}</div>
|
|
<div class="text-xs text-base-content/70">Expires: ${profile.expiration ? new Date(profile.expiration).toLocaleDateString() : 'Unknown'}</div>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mt-4">
|
|
<div class="stat">
|
|
<div class="stat-title text-xs">Library Size</div>
|
|
<div class="stat-value text-sm">${formatNumber(library.total || 0)}</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-title text-xs">Bad Torrents</div>
|
|
<div class="stat-value text-sm text-error">${formatNumber(library.bad || 0)}</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-title text-xs">Active Links</div>
|
|
<div class="stat-value text-sm text-success">${formatNumber(library.active_links || 0)}</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-title text-xs">Total Accounts</div>
|
|
<div class="stat-value text-sm text-info">${accounts.length}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Add accounts section if there are accounts
|
|
if (accounts && accounts.length > 0) {
|
|
html += `
|
|
<div class="mt-6">
|
|
<h4 class="text-lg font-semibold mb-3">
|
|
<i class="bi bi-person-lines-fill text-primary"></i>
|
|
Accounts
|
|
</h4>
|
|
<div class="grid grid-cols-2 md:grid-cols-2 gap-2">
|
|
`;
|
|
|
|
accounts.forEach((account, index) => {
|
|
const statusBadge = account.disabled ?
|
|
'<span class="badge badge-error badge-sm">Disabled</span>' :
|
|
'<span class="badge badge-success badge-sm">Active</span>';
|
|
|
|
const inUseBadge = account.in_use ?
|
|
'<span class="badge badge-info badge-sm">In Use</span>' :
|
|
'';
|
|
|
|
html += `
|
|
<div class="card bg-base-100 compact">
|
|
<div class="card-body p-3">
|
|
<div class="flex justify-between items-start mb-2">
|
|
<div class="flex-1">
|
|
<div class="flex items-center gap-2">
|
|
<h5 class="font-medium text-sm">Account #${account.order + 1}</h5>
|
|
${statusBadge}
|
|
${inUseBadge}
|
|
</div>
|
|
<p class="text-xs text-base-content/70 mt-1">${account.username || 'No username'}</p>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-xs font-mono text-base-content/80">
|
|
Token: ${account.token_masked || '****'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 md:grid-cols-2 gap-2">
|
|
<div class="stat bg-base-200 rounded p-2">
|
|
<div class="stat-title text-xs">Traffic Used</div>
|
|
<div class="stat-value text-xs">${window.decypharrUtils.formatBytes(account.traffic_used || 0)}</div>
|
|
</div>
|
|
<div class="stat bg-base-200 rounded p-2">
|
|
<div class="stat-title text-xs">Links Count</div>
|
|
<div class="stat-value text-xs">${formatNumber(account.links_count || 0)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
html += '</div></div>';
|
|
}
|
|
|
|
html += `
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
html += '</div>';
|
|
debridContent.innerHTML = html;
|
|
}
|
|
|
|
function updateStats(stats) {
|
|
// System overview
|
|
document.getElementById('memory-used').textContent = stats.memory_used || '-';
|
|
document.getElementById('heap-alloc').textContent = `Heap: ${stats.heap_alloc_mb || '-'}`;
|
|
document.getElementById('goroutines').textContent = formatNumber(stats.goroutines || 0);
|
|
document.getElementById('gc-cycles').textContent = `GC: ${formatNumber(stats.gc_cycles || 0)} cycles`;
|
|
document.getElementById('num-cpu').textContent = stats.num_cpu || '-';
|
|
document.getElementById('arch').textContent = stats.arch || '-';
|
|
document.getElementById('os').textContent = stats.os || '-';
|
|
document.getElementById('go-version').textContent = stats.go_version || '-';
|
|
|
|
// Rclone stats
|
|
updateRcloneStats(stats.rclone);
|
|
|
|
// Debrid stats
|
|
updateDebridStats(stats.debrids);
|
|
}
|
|
|
|
function loadStats() {
|
|
showLoading();
|
|
|
|
fetch(`${window.urlBase}debug/stats`)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(stats => {
|
|
updateStats(stats);
|
|
showContent();
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading stats:', error);
|
|
showError(error.message || 'Failed to load statistics');
|
|
});
|
|
}
|
|
|
|
// Event listeners
|
|
refreshBtn.addEventListener('click', loadStats);
|
|
retryBtn.addEventListener('click', loadStats);
|
|
|
|
// Auto-refresh every 30 seconds
|
|
setInterval(loadStats, 30000);
|
|
|
|
// Initial load
|
|
loadStats();
|
|
});
|
|
</script>
|
|
{{ end }} |