Files
MediaFusion/resources/js/metrics.js
T

449 lines
17 KiB
JavaScript

let weeklyUploadersChart = null;
const formatDateToYYYYMMDD = (date) => {
return date.toISOString().split('T')[0];
};
const getStartOfWeek = (date) => {
const d = new Date(date);
const day = d.getDay();
const diff = d.getDate() - day + (day === 0 ? -6 : 1);
return new Date(d.setDate(diff));
};
const fetchData = async (url) => {
const response = await fetch(url);
return response.json();
};
const removeLoadingElement = (spinnerId) => {
const spinner = document.getElementById(spinnerId);
if (spinner) {
spinner.remove();
}
};
const renderSchedulerDetails = async () => {
const data = await fetchData('/metrics/scrapy-schedulers');
removeLoadingElement('schedulerChartsSkeleton');
const container = document.getElementById('schedulerChartsContainer');
container.innerHTML = ''; // Clear loading skeleton
const enabledWithStats = [];
const enabledWithoutStats = [];
const disabledWithStats = [];
const disabledWithoutStats = [];
data.forEach(scheduler => {
if (scheduler.is_scheduler_disabled) {
if (scheduler.last_run_state) {
disabledWithStats.push(scheduler);
} else {
disabledWithoutStats.push(scheduler);
}
} else {
if (scheduler.last_run_state) {
enabledWithStats.push(scheduler);
} else {
enabledWithoutStats.push(scheduler);
}
}
});
const createSchedulerSection = (schedulers, sectionTitle) => {
const section = document.createElement('div');
section.className = 'scheduler-section';
const sectionTitleElem = document.createElement('h5');
sectionTitleElem.textContent = sectionTitle;
section.appendChild(sectionTitleElem);
let row;
schedulers.forEach((scheduler, index) => {
if (index % 3 === 0) {
row = document.createElement('div');
row.className = 'row';
section.appendChild(row);
}
const schedulerCard = document.createElement('div');
schedulerCard.className = 'col-lg-4 col-md-6 col-sm-12 mb-4';
let cardClass = 'card-scheduler ';
if (scheduler.is_scheduler_disabled) {
cardClass += 'disabled';
} else if (scheduler.last_run_state === null) {
cardClass += 'enabled';
} else {
const logCounts = {
info: scheduler.last_run_state.log_count_info, warning: scheduler.last_run_state.log_count_warning, error: scheduler.last_run_state.log_count_error
};
const maxLogCount = Math.max(logCounts.info, logCounts.warning, logCounts.error);
if (maxLogCount === logCounts.error) {
cardClass += 'log-error';
} else if (maxLogCount === logCounts.warning) {
cardClass += 'log-warning';
} else {
cardClass += 'log-info';
}
}
const lastRunState = scheduler.last_run_state ? `
<p>Items Scraped: ${scheduler.last_run_state.item_scraped_count}</p>
<p>Items Dropped: ${scheduler.last_run_state.item_dropped_count}</p>
<p class="text-info">Info: ${scheduler.last_run_state.log_count_info}</p>
<p class="text-warning">Warning: ${scheduler.last_run_state.log_count_warning}</p>
<p class="text-danger">Error: ${scheduler.last_run_state.log_count_error}</p>
` : '';
const cardContent = `
<div class="${cardClass}">
<h5>${scheduler.name}</h5>
<p>Last Run: ${scheduler.time_since_last_run}</p>
<p>${scheduler.is_scheduler_disabled ? 'Scheduler Disabled' : `Next Schedule In: ${scheduler.next_schedule_in}`}</p>
${lastRunState}
</div>
`;
schedulerCard.innerHTML = cardContent;
row.appendChild(schedulerCard);
});
container.appendChild(section);
};
if (enabledWithStats.length > 0) {
createSchedulerSection(enabledWithStats, 'Enabled Schedulers with Stats');
}
if (enabledWithoutStats.length > 0) {
createSchedulerSection(enabledWithoutStats, 'Enabled Schedulers without Stats');
}
if (disabledWithStats.length > 0) {
createSchedulerSection(disabledWithStats, 'Disabled Schedulers with Stats');
}
if (disabledWithoutStats.length > 0) {
createSchedulerSection(disabledWithoutStats, 'Disabled Schedulers without Stats');
}
};
const renderMetadataCountsChart = async () => {
const data = await fetchData('/metrics/metadata');
removeLoadingElement('metadataCountsSkeleton');
const ctx = document.getElementById('metadataCountsChart').getContext('2d');
new Chart(ctx, {
type: 'pie', data: {
labels: ['Movies', 'Series', 'TV Channels'], datasets: [{
label: 'Metadata Counts',
data: [data.movies, data.series, data.tv_channels],
backgroundColor: ['rgba(153, 102, 255, 0.6)', 'rgba(54, 162, 235, 0.6)', 'rgba(75, 192, 192, 0.6)'],
borderColor: ['rgba(153, 102, 255, 1)', 'rgba(54, 162, 235, 1)', 'rgba(75, 192, 192, 1)'],
}]
}, options: {
responsive: true, maintainAspectRatio: false, plugins: {
legend: {
display: false
}, datalabels: {
formatter: (value, context) => {
return context.chart.data.labels[context.dataIndex] + ': ' + value.toLocaleString();
}, color: '#fff', anchor: 'center', align: 'center', offset: 0, borderRadius: 4, backgroundColor: (context) => {
return context.dataset.backgroundColor;
}, font: {
weight: 'bold'
}
}
}
}
});
};
const renderTotalTorrentsCount = async () => {
const data = await fetchData('/metrics/torrents');
removeLoadingElement('totalTorrentsSkeleton');
const totalTorrentsValue = document.getElementById('totalTorrentsValue');
totalTorrentsValue.textContent = `${data.total_torrents.toLocaleString()} (${data.total_torrents_readable})`;
};
const renderTorrentSourcesChart = async () => {
const data = await fetchData('/metrics/torrents/sources');
removeLoadingElement('torrentSourcesSkeleton');
const ctx = document.getElementById('torrentSourcesChart').getContext('2d');
new Chart(ctx, {
type: 'bar', data: {
datasets: data.map(source => {
return {
label: source.name, data: [{x: source.name, y: source.count}],
};
}),
}, options: {
grouped: false, responsive: true, maintainAspectRatio: false, scales: {
y: {
beginAtZero: true, display: false
}, x: {
ticks: {
color: '#fff',
}
}
}, layout: {
padding: {
top: 50 // Add padding to prevent label cutoff
}
}, plugins: {
legend: {
display: false
}, tooltip: {
callbacks: {
label: function (context) {
return `${context.raw.y.toLocaleString()} Torrents`;
}
}
}, datalabels: {
align: 'end', anchor: 'end', rotation: 0, color: '#fff', formatter: (value, context) => {
return value.y.toLocaleString();
}, font: {
weight: 'bold'
},
}
}
}
});
};
const renderTorrentUploadersChart = async () => {
const data = await fetchData('/metrics/torrents/uploaders');
removeLoadingElement('torrentUploadersSkeleton');
const ctx = document.getElementById('torrentUploadersChart').getContext('2d');
const sortedData = data.sort((a, b) => b.count - a.count);
new Chart(ctx, {
type: 'bar', data: {
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, layout: {
padding: {
right: 50 // Add padding to prevent label cutoff
}
}, 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: {
beginAtZero: true, grid: {
color: 'rgba(255, 255, 255, 0.1)'
}, ticks: {
color: '#fff'
}
}, y: {
grid: {
display: false
}, ticks: {
color: '#fff'
}
}
}
}
});
};
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: 'Number of Cached Torrents', color: '#fff'
}, ticks: {
color: '#fff'
}, grid: {
color: 'rgba(255, 255, 255, 0.1)'
}
}, x: {
ticks: {
color: '#fff'
}, grid: {
display: false
}
}
}
}
});
};
const renderWeeklyUploadersChart = async (selectedDate = null) => {
if (!selectedDate) {
selectedDate = new Date();
}
const startOfWeek = getStartOfWeek(selectedDate);
const formattedDate = formatDateToYYYYMMDD(startOfWeek);
try {
// Show loading state
document.getElementById('weeklyUploadersSkeleton').style.display = 'block';
if (weeklyUploadersChart) {
weeklyUploadersChart.destroy();
}
const data = await fetchData(`/metrics/torrents/uploaders/weekly/${formattedDate}`);
if (data.error) {
console.error('Error fetching data:', data.error);
return;
}
// Update date range display
const weekStart = new Date(data.week_start);
const weekEnd = new Date(data.week_end);
const dateRangeElement = document.getElementById('weekDateRange');
dateRangeElement.innerHTML = `
<span class="text-white">
${weekStart.toLocaleDateString()} - ${weekEnd.toLocaleDateString()}
</span>
`;
// Update week selector value
document.getElementById('weekSelector').value = formattedDate;
const ctx = document.getElementById('weeklyUploadersChart').getContext('2d');
weeklyUploadersChart = new Chart(ctx, {
type: 'bar', data: {
labels: data.uploaders.map(uploader => uploader.name), datasets: [{
data: data.uploaders.map(uploader => uploader.count), backgroundColor: 'rgba(147, 112, 219, 0.6)', borderColor: 'rgba(147, 112, 219, 1)', borderWidth: 1
}]
}, options: {
indexAxis: 'y', responsive: true, maintainAspectRatio: false, layout: {
padding: {
right: 50
}
}, 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}`, `Last Upload: ${lastUpload.toLocaleDateString()}`];
}
}
}, datalabels: {
color: '#fff', anchor: 'end', align: 'right', formatter: (value) => value, font: {
weight: 'bold', size: 12
}, padding: {
right: 6
}
}
}, 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'
}
}
}
}
});
} finally {
// Hide loading state
document.getElementById('weeklyUploadersSkeleton').style.display = 'none';
}
};
document.addEventListener('DOMContentLoaded', function () {
const weekSelector = document.getElementById('weekSelector');
const prevWeekBtn = document.getElementById('prevWeek');
const nextWeekBtn = document.getElementById('nextWeek');
// Set initial date to start of current week
const currentDate = new Date();
const startOfWeek = getStartOfWeek(currentDate);
weekSelector.value = formatDateToYYYYMMDD(startOfWeek);
// Event listeners for navigation
weekSelector.addEventListener('change', (e) => {
renderWeeklyUploadersChart(new Date(e.target.value));
});
prevWeekBtn.addEventListener('click', () => {
const currentDate = new Date(weekSelector.value);
const prevWeek = new Date(currentDate.setDate(currentDate.getDate() - 7));
renderWeeklyUploadersChart(prevWeek);
});
nextWeekBtn.addEventListener('click', () => {
const currentDate = new Date(weekSelector.value);
const nextWeek = new Date(currentDate.setDate(currentDate.getDate() + 7));
renderWeeklyUploadersChart(nextWeek);
});
// Initial render
renderWeeklyUploadersChart();
});
document.addEventListener('DOMContentLoaded', function () {
const initCharts = () => {
renderSchedulerDetails();
renderMetadataCountsChart();
renderTotalTorrentsCount();
renderTorrentSourcesChart();
renderTorrentUploadersChart();
renderDebridCacheMetrics();
};
Chart.register(ChartDataLabels);
initCharts();
});