mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
Add charts for top torrent uploaders and weekly top uploader data & improve debrid cache & torrent source charts
Introduced new visualizations to display the top 20 torrent uploaders and their weekly activity. Enhanced the styling for metrics sections and updated backend logic to support these additions, ensuring cleaner and more focused data retrieval.
This commit is contained in:
+12
-32
@@ -110,14 +110,10 @@ async def get_redis_metrics() -> Dict[str, Any]:
|
||||
|
||||
async def get_debrid_cache_metrics() -> Dict[str, Any]:
|
||||
"""
|
||||
Get detailed metrics about debrid cache usage.
|
||||
Get simplified metrics about debrid cache usage, focusing only on cached torrents per service.
|
||||
"""
|
||||
metrics = {
|
||||
"timestamp": datetime.now(tz=timezone.utc).isoformat(),
|
||||
"total_memory_usage": 0,
|
||||
"total_cached_torrents": 0,
|
||||
"services": {},
|
||||
}
|
||||
metrics = {"timestamp": datetime.now(tz=timezone.utc).isoformat(), "services": {}}
|
||||
|
||||
debrid_services = [
|
||||
"alldebrid",
|
||||
"debridlink",
|
||||
@@ -132,37 +128,21 @@ async def get_debrid_cache_metrics() -> Dict[str, Any]:
|
||||
]
|
||||
|
||||
try:
|
||||
|
||||
for service in debrid_services:
|
||||
cache_key = f"debrid_cache:{service}"
|
||||
|
||||
# Get various metrics for each service
|
||||
cache_size = await REDIS_ASYNC_CLIENT.hlen(cache_key)
|
||||
memory_usage = await REDIS_ASYNC_CLIENT.memory_usage(cache_key) or 0
|
||||
|
||||
metrics["services"][service] = {
|
||||
"cached_torrents": cache_size,
|
||||
"memory_usage": memory_usage,
|
||||
}
|
||||
if cache_size > 0: # Only include services with cached torrents
|
||||
metrics["services"][service] = {"cached_torrents": cache_size}
|
||||
|
||||
# Update totals
|
||||
metrics["total_cached_torrents"] += cache_size
|
||||
metrics["total_memory_usage"] += memory_usage
|
||||
|
||||
# Add human readable total memory
|
||||
metrics["total_memory_usage_human"] = humanize.naturalsize(
|
||||
metrics["total_memory_usage"]
|
||||
)
|
||||
|
||||
# Calculate which service has most cached torrents
|
||||
if metrics["services"]:
|
||||
most_cached = max(
|
||||
metrics["services"].items(), key=lambda x: x[1]["cached_torrents"]
|
||||
# Sort services by cache size
|
||||
metrics["services"] = dict(
|
||||
sorted(
|
||||
metrics["services"].items(),
|
||||
key=lambda x: x[1]["cached_torrents"],
|
||||
reverse=True,
|
||||
)
|
||||
metrics["most_used_service"] = {
|
||||
"name": most_cached[0],
|
||||
"cached_count": most_cached[1]["cached_torrents"],
|
||||
}
|
||||
)
|
||||
|
||||
return metrics
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
import humanize
|
||||
from fastapi import APIRouter, Request, Response
|
||||
@@ -65,6 +66,7 @@ async def get_torrents_by_sources(response: Response):
|
||||
[
|
||||
{"$group": {"_id": "$source", "count": {"$sum": 1}}},
|
||||
{"$sort": {"count": -1}},
|
||||
{"$limit": 20}, # Limit to top 20 sources
|
||||
]
|
||||
).to_list()
|
||||
torrent_sources = [
|
||||
@@ -151,3 +153,65 @@ async def debrid_cache_metrics():
|
||||
Returns statistics about cache size, memory usage, and usage patterns per service.
|
||||
"""
|
||||
return await get_debrid_cache_metrics()
|
||||
|
||||
|
||||
@metrics_router.get("/torrents/uploaders", tags=["metrics"])
|
||||
async def get_torrents_by_uploaders(response: Response):
|
||||
response.headers.update(const.NO_CACHE_HEADERS)
|
||||
results = await TorrentStreams.aggregate(
|
||||
[
|
||||
{"$match": {"uploader": {"$ne": None}}},
|
||||
{"$group": {"_id": "$uploader", "count": {"$sum": 1}}},
|
||||
{"$sort": {"count": -1}},
|
||||
{"$limit": 20}, # Limit to top 20 uploaders
|
||||
]
|
||||
).to_list()
|
||||
|
||||
uploader_stats = [
|
||||
{"name": stat["_id"] if stat["_id"] else "Unknown", "count": stat["count"]}
|
||||
for stat in results
|
||||
]
|
||||
return uploader_stats
|
||||
|
||||
|
||||
@metrics_router.get("/torrents/uploaders/weekly", tags=["metrics"])
|
||||
async def get_weekly_top_uploaders(response: Response):
|
||||
response.headers.update(const.NO_CACHE_HEADERS)
|
||||
|
||||
# Calculate the start of the current week (Monday)
|
||||
today = datetime.now(tz=timezone.utc)
|
||||
start_of_week = today - timedelta(days=today.weekday())
|
||||
start_of_week = start_of_week.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
results = await TorrentStreams.aggregate(
|
||||
[
|
||||
{
|
||||
"$match": {
|
||||
"uploader": {"$ne": None},
|
||||
"created_at": {"$gte": start_of_week},
|
||||
}
|
||||
},
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$uploader",
|
||||
"count": {"$sum": 1},
|
||||
"latest_upload": {"$max": "$created_at"},
|
||||
}
|
||||
},
|
||||
{"$sort": {"count": -1}},
|
||||
{"$limit": 20},
|
||||
]
|
||||
).to_list()
|
||||
|
||||
uploaders_stats = [
|
||||
{
|
||||
"name": stat["_id"],
|
||||
"count": stat["count"],
|
||||
"latest_upload": (
|
||||
stat["latest_upload"].isoformat() if stat["latest_upload"] else None
|
||||
),
|
||||
}
|
||||
for stat in results
|
||||
]
|
||||
|
||||
return {"week_start": start_of_week.isoformat(), "uploaders": uploaders_stats}
|
||||
|
||||
+59
-17
@@ -13,6 +13,14 @@ body, html {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 2px solid rgba(74, 71, 163, 0.5);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.config-container {
|
||||
margin: 2% auto;
|
||||
padding: 2%;
|
||||
@@ -38,7 +46,37 @@ body, html {
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
margin-bottom: 2rem;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.chart-section:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.date-info {
|
||||
background: rgba(129,87,208,0.6);
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.chart-wrapper canvas {
|
||||
position: absolute;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
@@ -49,19 +87,6 @@ body, html {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding-bottom: 50%;
|
||||
}
|
||||
|
||||
.chart-wrapper canvas {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 300px;
|
||||
display: block;
|
||||
@@ -69,9 +94,14 @@ body, html {
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #4a47a3;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
background: linear-gradient(145deg, #4a47a3, #2d2a6a);
|
||||
border: none;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.card-text {
|
||||
@@ -178,6 +208,18 @@ body, html {
|
||||
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.chart-wrapper {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.config-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
+19
-22
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
|
||||
<div class="chart-section">
|
||||
<h4 class="text-left">Torrent Count By Sources</h4>
|
||||
<h4 class="text-left">Top 20 Torrents Sources</h4>
|
||||
<div class="chart-wrapper">
|
||||
<canvas id="torrentSourcesChart"></canvas>
|
||||
<div class="skeleton-loader" id="torrentSourcesSkeleton"></div>
|
||||
@@ -56,30 +56,27 @@
|
||||
</div>
|
||||
|
||||
<div class="chart-section">
|
||||
<h4 class="text-left">Debrid Cache Statistics</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card text-white">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Total Cached Torrents Count (Not Unique)</h5>
|
||||
<p id="totalCachedTorrentsValue" class="card-text large-number"></p>
|
||||
<div class="skeleton-loader" id="debridTotalsSkeleton"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card text-white">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Total Cache Memory Usage</h5>
|
||||
<p id="totalCacheMemoryValue" class="card-text large-number"></p>
|
||||
<div class="skeleton-loader" id="debridMemorySkeleton"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="text-left">Top 20 Torrents Uploaders</h4>
|
||||
<div class="chart-wrapper">
|
||||
<canvas id="torrentUploadersChart"></canvas>
|
||||
<div class="skeleton-loader" id="torrentUploadersSkeleton"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-section">
|
||||
<h4 class="text-left">Top 20 Uploaders This Week</h4>
|
||||
<div class="date-info mb-3" id="weekDateRange"></div>
|
||||
<div class="chart-wrapper">
|
||||
<canvas id="weeklyUploadersChart"></canvas>
|
||||
<div class="skeleton-loader" id="weeklyUploadersSkeleton"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-section">
|
||||
<h4 class="text-left">Debrid Cache Statistics</h4>
|
||||
<div class="card text-white">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Cache Distribution</h5>
|
||||
<h5 class="card-title">Cached Torrents by Service</h5>
|
||||
<div class="chart-wrapper">
|
||||
<canvas id="debridCacheChart"></canvas>
|
||||
<div class="skeleton-loader" id="debridChartSkeleton"></div>
|
||||
|
||||
+197
-77
@@ -230,110 +230,228 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
};
|
||||
|
||||
const renderDebridCacheMetrics = async () => {
|
||||
const data = await fetchData('/metrics/debrid-cache');
|
||||
const renderTorrentUploadersChart = async () => {
|
||||
const data = await fetchData('/metrics/torrents/uploaders');
|
||||
removeLoadingElement('torrentUploadersSkeleton');
|
||||
const ctx = document.getElementById('torrentUploadersChart').getContext('2d');
|
||||
|
||||
// Remove loading skeletons
|
||||
removeLoadingElement('debridTotalsSkeleton');
|
||||
removeLoadingElement('debridMemorySkeleton');
|
||||
removeLoadingElement('debridChartSkeleton');
|
||||
|
||||
// Update total values
|
||||
const totalCachedTorrents = document.getElementById('totalCachedTorrentsValue');
|
||||
totalCachedTorrents.textContent = data.total_cached_torrents.toLocaleString();
|
||||
|
||||
const totalMemoryUsage = document.getElementById('totalCacheMemoryValue');
|
||||
totalMemoryUsage.textContent = data.total_memory_usage_human;
|
||||
|
||||
// Create the stacked bar chart for service comparison
|
||||
const ctx = document.getElementById('debridCacheChart').getContext('2d');
|
||||
|
||||
// Prepare data for the chart
|
||||
const services = Object.keys(data.services);
|
||||
const cachedTorrents = services.map(service => data.services[service].cached_torrents);
|
||||
const memoryUsages = services.map(service => data.services[service].memory_usage / (1024 * 1024)); // Convert to MB
|
||||
const sortedData = data.sort((a, b) => b.count - a.count);
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: services,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Cached Torrents',
|
||||
data: cachedTorrents,
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
borderWidth: 1,
|
||||
yAxisID: 'y'
|
||||
},
|
||||
{
|
||||
label: 'Memory Usage (MB)',
|
||||
data: memoryUsages,
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.6)',
|
||||
borderColor: 'rgba(255, 99, 132, 1)',
|
||||
borderWidth: 1,
|
||||
yAxisID: 'y1'
|
||||
}
|
||||
]
|
||||
labels: sortedData.map(item => item.name),
|
||||
datasets: [{
|
||||
data: sortedData.map(item => item.count),
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.6)',
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
return `${context.raw.toLocaleString()} Torrents`;
|
||||
}
|
||||
}
|
||||
},
|
||||
datalabels: {
|
||||
color: '#fff',
|
||||
anchor: 'end',
|
||||
align: 'end',
|
||||
formatter: (value) => value.toLocaleString(),
|
||||
font: {
|
||||
weight: 'bold'
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
y: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Cached Torrents',
|
||||
color: '#fff'
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: 'rgba(255, 255, 255, 0.1)'
|
||||
},
|
||||
ticks: {
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'right',
|
||||
y: {
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const renderWeeklyUploadersChart = async () => {
|
||||
const data = await fetchData('/metrics/torrents/uploaders/weekly');
|
||||
removeLoadingElement('weeklyUploadersSkeleton');
|
||||
|
||||
// Update date range display
|
||||
const weekStart = new Date(data.week_start);
|
||||
const weekEnd = new Date(weekStart);
|
||||
weekEnd.setDate(weekEnd.getDate() + 6);
|
||||
|
||||
const dateRangeElement = document.getElementById('weekDateRange');
|
||||
dateRangeElement.innerHTML = `
|
||||
<span class="text-white">
|
||||
${weekStart.toLocaleDateString()} - ${weekEnd.toLocaleDateString()}
|
||||
</span>
|
||||
`;
|
||||
|
||||
const ctx = document.getElementById('weeklyUploadersChart').getContext('2d');
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: data.uploaders.map(uploader => uploader.name),
|
||||
datasets: [{
|
||||
data: data.uploaders.map(uploader => uploader.count),
|
||||
backgroundColor: 'rgba(122,76,204,0.6)',
|
||||
borderColor: 'rgba(129,87,208,0.6)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
const uploader = data.uploaders[context.dataIndex];
|
||||
const lastUpload = new Date(uploader.latest_upload);
|
||||
return [
|
||||
`Torrents: ${context.raw.toLocaleString()}`,
|
||||
`Last Upload: ${lastUpload.toLocaleString()}`
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
datalabels: {
|
||||
color: '#fff',
|
||||
anchor: 'end',
|
||||
align: 'end',
|
||||
formatter: (value) => value.toLocaleString(),
|
||||
font: {
|
||||
weight: 'bold'
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: 'rgba(255, 255, 255, 0.1)'
|
||||
},
|
||||
ticks: {
|
||||
color: '#fff',
|
||||
callback: function (value) {
|
||||
return value.toLocaleString();
|
||||
}
|
||||
}
|
||||
},
|
||||
y: {
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
color: '#fff',
|
||||
font: {
|
||||
size: 12
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const renderDebridCacheMetrics = async () => {
|
||||
const data = await fetchData('/metrics/debrid-cache');
|
||||
removeLoadingElement('debridChartSkeleton');
|
||||
|
||||
// Create the bar chart for service comparison
|
||||
const ctx = document.getElementById('debridCacheChart').getContext('2d');
|
||||
|
||||
// Prepare data for the chart
|
||||
const services = Object.keys(data.services);
|
||||
const cachedTorrents = services.map(service => data.services[service].cached_torrents);
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: services,
|
||||
datasets: [{
|
||||
label: 'Cached Torrents',
|
||||
data: cachedTorrents,
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
return `Cached Torrents: ${context.raw.toLocaleString()}`;
|
||||
}
|
||||
}
|
||||
},
|
||||
datalabels: {
|
||||
color: '#fff',
|
||||
anchor: 'end',
|
||||
align: 'end',
|
||||
formatter: (value) => value.toLocaleString(),
|
||||
font: {
|
||||
weight: 'bold'
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Memory Usage (MB)',
|
||||
text: 'Number of Cached Torrents',
|
||||
color: '#fff'
|
||||
},
|
||||
ticks: {
|
||||
color: '#fff'
|
||||
},
|
||||
grid: {
|
||||
drawOnChartArea: false
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: '#fff'
|
||||
color: 'rgba(255, 255, 255, 0.1)'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
if (context.datasetIndex === 0) {
|
||||
return `Cached Torrents: ${context.raw.toLocaleString()}`;
|
||||
} else {
|
||||
return `Memory Usage: ${context.raw.toFixed(2)} MB`;
|
||||
}
|
||||
}
|
||||
x: {
|
||||
ticks: {
|
||||
color: '#fff'
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,6 +464,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
renderMetadataCountsChart();
|
||||
renderTotalTorrentsCount();
|
||||
renderTorrentSourcesChart();
|
||||
renderTorrentUploadersChart();
|
||||
renderWeeklyUploadersChart();
|
||||
renderDebridCacheMetrics();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user