mirror of
https://github.com/plexguide/Huntarr.io.git
synced 2026-04-20 09:37:07 -04:00
358 lines
15 KiB
JavaScript
358 lines
15 KiB
JavaScript
/**
|
|
* Prowlarr Module
|
|
* Handles Prowlarr-specific functionality
|
|
*/
|
|
|
|
window.HuntarrProwlarr = {
|
|
prowlarrStatsInterval: null,
|
|
currentIndexerStats: null,
|
|
|
|
loadProwlarrStatus: function() {
|
|
const prowlarrCard = document.getElementById('prowlarrStatusCard');
|
|
if (!prowlarrCard) return;
|
|
|
|
// First check if Prowlarr is configured and enabled
|
|
HuntarrUtils.fetchWithTimeout('./api/prowlarr/status')
|
|
.then(response => response.json())
|
|
.then(statusData => {
|
|
// Only show card if Prowlarr is configured and enabled
|
|
if (statusData.configured && statusData.enabled) {
|
|
prowlarrCard.style.display = 'block';
|
|
|
|
// Update connection status
|
|
const statusElement = document.getElementById('prowlarrConnectionStatus');
|
|
if (statusElement) {
|
|
if (statusData.connected) {
|
|
statusElement.textContent = '🟢 Connected';
|
|
statusElement.className = 'status-badge connected';
|
|
} else {
|
|
statusElement.textContent = '🔴 Disconnected';
|
|
statusElement.className = 'status-badge error';
|
|
}
|
|
}
|
|
|
|
// Load data if connected
|
|
if (statusData.connected) {
|
|
// Load indexers quickly first
|
|
this.loadProwlarrIndexers();
|
|
// Load statistics separately (cached)
|
|
this.loadProwlarrStats();
|
|
|
|
// Set up periodic refresh for statistics (every 5 minutes)
|
|
if (!this.prowlarrStatsInterval) {
|
|
this.prowlarrStatsInterval = setInterval(() => {
|
|
this.loadProwlarrStats();
|
|
}, 5 * 60 * 1000); // 5 minutes
|
|
}
|
|
} else {
|
|
// Show disconnected state
|
|
this.updateIndexersList(null, 'Prowlarr is disconnected');
|
|
this.updateProwlarrStatistics(null, 'Prowlarr is disconnected');
|
|
|
|
// Clear interval if disconnected
|
|
if (this.prowlarrStatsInterval) {
|
|
clearInterval(this.prowlarrStatsInterval);
|
|
this.prowlarrStatsInterval = null;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// Hide card if not configured or disabled
|
|
prowlarrCard.style.display = 'none';
|
|
console.log('[HuntarrProwlarr] Prowlarr card hidden - configured:', statusData.configured, 'enabled:', statusData.enabled);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading Prowlarr status:', error);
|
|
// Hide card on error
|
|
prowlarrCard.style.display = 'none';
|
|
});
|
|
},
|
|
|
|
loadProwlarrIndexers: function() {
|
|
HuntarrUtils.fetchWithTimeout('./api/prowlarr/indexers')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success && data.indexer_details) {
|
|
this.updateIndexersList(data.indexer_details);
|
|
} else {
|
|
console.error('Failed to load Prowlarr indexers:', data.error);
|
|
this.updateIndexersList(null, data.error || 'Failed to load indexers');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading Prowlarr indexers:', error);
|
|
this.updateIndexersList(null, 'Connection error');
|
|
});
|
|
},
|
|
|
|
loadProwlarrStats: function() {
|
|
HuntarrUtils.fetchWithTimeout('./api/prowlarr/stats')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success && data.stats) {
|
|
const normalizedStats = this.normalizeStatsPayload(data.stats);
|
|
this.currentIndexerStats = normalizedStats;
|
|
this.updateProwlarrStatistics(normalizedStats);
|
|
} else {
|
|
console.error('Failed to load Prowlarr stats:', data.error);
|
|
this.updateProwlarrStatistics(null, data.error || 'Failed to load stats');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading Prowlarr stats:', error);
|
|
this.updateProwlarrStatistics(null, 'Connection error');
|
|
});
|
|
},
|
|
|
|
normalizeStatsPayload: function(rawStats) {
|
|
if (!rawStats || typeof rawStats !== 'object') {
|
|
return null;
|
|
}
|
|
|
|
// Already in the legacy UI format expected by this module.
|
|
if (rawStats.overall && rawStats.indexers) {
|
|
return rawStats;
|
|
}
|
|
|
|
// Newer backend payload shape: flat keys + individual_indexer_stats/indexer_performance.
|
|
const indexersByName = {};
|
|
const individual = rawStats.individual_indexer_stats || {};
|
|
Object.keys(individual).forEach((name) => {
|
|
const idx = individual[name] || {};
|
|
indexersByName[name] = {
|
|
total_queries: Number(idx.queries ?? idx.searches_today ?? 0) || 0,
|
|
total_grabs: Number(idx.grabs ?? idx.successful_today ?? 0) || 0,
|
|
avg_response_time: Number(idx.response_time ?? 0) || 0
|
|
};
|
|
});
|
|
|
|
// Fallback when individual stats aren't present but indexer_performance is.
|
|
if (Object.keys(indexersByName).length === 0 && Array.isArray(rawStats.indexer_performance)) {
|
|
rawStats.indexer_performance.forEach((idx) => {
|
|
const name = idx && idx.name ? idx.name : 'Unknown';
|
|
indexersByName[name] = {
|
|
total_queries: Number(idx.queries ?? idx.searches_today ?? 0) || 0,
|
|
total_grabs: Number(idx.grabs ?? idx.successful_today ?? 0) || 0,
|
|
avg_response_time: Number(idx.response_time ?? 0) || 0
|
|
};
|
|
});
|
|
}
|
|
|
|
const totalIndexers = Object.keys(indexersByName).length || Number(rawStats.total_indexers || 0) || 0;
|
|
const totalQueries = Number(rawStats.searches_today ?? rawStats.total_queries ?? 0) || 0;
|
|
const totalGrabs = Number(rawStats.grabs_today ?? rawStats.total_grabs ?? 0) || 0;
|
|
const successRate = Number(rawStats.recent_success_rate ?? 0) || 0;
|
|
const failedSearches = Number(rawStats.recent_failed_searches ?? 0) || 0;
|
|
const avgResponseTime = Number(rawStats.avg_response_time ?? 0) || 0;
|
|
|
|
return {
|
|
overall: {
|
|
total_queries: totalQueries,
|
|
total_grabs: totalGrabs,
|
|
total_indexers: totalIndexers,
|
|
success_rate: successRate,
|
|
failed_searches: failedSearches,
|
|
avg_response_time: avgResponseTime
|
|
},
|
|
indexers: indexersByName
|
|
};
|
|
},
|
|
|
|
updateIndexersList: function(indexerDetails, errorMessage = null) {
|
|
const indexersList = document.getElementById('prowlarr-indexers-list');
|
|
if (!indexersList) return;
|
|
|
|
if (errorMessage) {
|
|
indexersList.innerHTML = `<div class="loading-text" style="color: #ef4444;">${errorMessage}</div>`;
|
|
return;
|
|
}
|
|
|
|
if (!indexerDetails || (!indexerDetails.active && !indexerDetails.throttled && !indexerDetails.failed)) {
|
|
indexersList.innerHTML = '<div class="loading-text">No indexers configured</div>';
|
|
return;
|
|
}
|
|
|
|
// Combine all indexers and sort alphabetically
|
|
let allIndexers = [];
|
|
|
|
if (indexerDetails.active) {
|
|
allIndexers = allIndexers.concat(
|
|
indexerDetails.active.map(idx => ({ ...idx, status: 'active' }))
|
|
);
|
|
}
|
|
|
|
if (indexerDetails.throttled) {
|
|
allIndexers = allIndexers.concat(
|
|
indexerDetails.throttled.map(idx => ({ ...idx, status: 'throttled' }))
|
|
);
|
|
}
|
|
|
|
if (indexerDetails.failed) {
|
|
allIndexers = allIndexers.concat(
|
|
indexerDetails.failed.map(idx => ({ ...idx, status: 'failed' }))
|
|
);
|
|
}
|
|
|
|
// Sort alphabetically by name
|
|
allIndexers.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
if (allIndexers.length === 0) {
|
|
indexersList.innerHTML = '<div class="loading-text">No indexers found</div>';
|
|
return;
|
|
}
|
|
|
|
// Build the HTML for indexers list with hover interactions
|
|
const indexersHtml = allIndexers.map(indexer => {
|
|
const statusText = indexer.status === 'active' ? 'Active' :
|
|
indexer.status === 'throttled' ? 'Throttled' :
|
|
'Failed';
|
|
|
|
return `
|
|
<div class="indexer-item" data-indexer-name="${indexer.name}">
|
|
<span class="indexer-name hoverable">${indexer.name}</span>
|
|
<span class="indexer-status ${indexer.status}">${statusText}</span>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
indexersList.innerHTML = indexersHtml;
|
|
|
|
// Add hover event listeners to indexer names
|
|
const indexerItems = indexersList.querySelectorAll('.indexer-item');
|
|
indexerItems.forEach(item => {
|
|
const indexerName = item.dataset.indexerName;
|
|
const nameElement = item.querySelector('.indexer-name');
|
|
|
|
nameElement.addEventListener('mouseenter', () => {
|
|
this.showIndexerStats(indexerName);
|
|
nameElement.classList.add('hovered');
|
|
});
|
|
|
|
nameElement.addEventListener('mouseleave', () => {
|
|
this.showOverallStats();
|
|
nameElement.classList.remove('hovered');
|
|
});
|
|
});
|
|
},
|
|
|
|
updateProwlarrStatistics: function(stats, errorMessage = null) {
|
|
const statisticsContent = document.getElementById('prowlarr-statistics-content');
|
|
if (!statisticsContent) return;
|
|
|
|
if (errorMessage) {
|
|
statisticsContent.innerHTML = `<div class="loading-text" style="color: #ef4444;">${errorMessage}</div>`;
|
|
return;
|
|
}
|
|
|
|
if (!stats) {
|
|
statisticsContent.innerHTML = '<div class="loading-text">No statistics available</div>';
|
|
return;
|
|
}
|
|
|
|
// Store stats for hover functionality
|
|
this.currentIndexerStats = stats;
|
|
|
|
// Show overall stats by default
|
|
this.showOverallStats();
|
|
},
|
|
|
|
showIndexerStats: function(indexerName) {
|
|
if (!this.currentIndexerStats || !this.currentIndexerStats.indexers) return;
|
|
|
|
const indexerStats = this.currentIndexerStats.indexers[indexerName];
|
|
if (!indexerStats) return;
|
|
|
|
const statisticsContent = document.getElementById('prowlarr-statistics-content');
|
|
if (!statisticsContent) return;
|
|
|
|
const formatNumber = window.HuntarrStats ?
|
|
window.HuntarrStats.formatLargeNumber.bind(window.HuntarrStats) :
|
|
(n => n.toLocaleString());
|
|
const formatExactNumber = (n) => {
|
|
const v = Number(n || 0);
|
|
return Number.isFinite(v) ? String(Math.round(v)) : '0';
|
|
};
|
|
|
|
statisticsContent.innerHTML = `
|
|
<div class="stat-card">
|
|
<div class="stat-label">SEARCHES (24H)</div>
|
|
<div class="stat-value success">${formatExactNumber(indexerStats.total_queries || 0)}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">GRABS (24H)</div>
|
|
<div class="stat-value success">${formatExactNumber(indexerStats.total_grabs || 0)}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">SUCCESS RATE</div>
|
|
<div class="stat-value success">${indexerStats.avg_response_time ? ((indexerStats.total_grabs || 0) / Math.max(indexerStats.total_queries || 1, 1) * 100).toFixed(1) + '%' : 'N/A'}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">AVG RESPONSE</div>
|
|
<div class="stat-value success">${indexerStats.avg_response_time ? Number(indexerStats.avg_response_time).toFixed(0) + 'ms' : 'N/A'}</div>
|
|
</div>
|
|
<div class="indexer-name-display">${indexerName}</div>
|
|
`;
|
|
},
|
|
|
|
showOverallStats: function() {
|
|
if (!this.currentIndexerStats || !this.currentIndexerStats.overall) return;
|
|
|
|
const statisticsContent = document.getElementById('prowlarr-statistics-content');
|
|
if (!statisticsContent) return;
|
|
|
|
const overall = this.currentIndexerStats.overall;
|
|
const formatNumber = window.HuntarrStats ?
|
|
window.HuntarrStats.formatLargeNumber.bind(window.HuntarrStats) :
|
|
(n => n.toLocaleString());
|
|
const formatExactNumber = (n) => {
|
|
const v = Number(n || 0);
|
|
return Number.isFinite(v) ? String(Math.round(v)) : '0';
|
|
};
|
|
|
|
statisticsContent.innerHTML = `
|
|
<div class="stat-card">
|
|
<div class="stat-label">SEARCHES (24H)</div>
|
|
<div class="stat-value success">${formatExactNumber(overall.total_queries || 0)}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">GRABS (24H)</div>
|
|
<div class="stat-value success">${formatExactNumber(overall.total_grabs || 0)}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">SUCCESS RATE</div>
|
|
<div class="stat-value success">${(Number(overall.success_rate || 0)).toFixed(1)}%</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">AVG RESPONSE</div>
|
|
<div class="stat-value success">${overall.avg_response_time ? Number(overall.avg_response_time).toFixed(0) + 'ms' : 'N/A'}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">FAILED TODAY</div>
|
|
<div class="stat-value error">${formatExactNumber(overall.failed_searches || 0)}</div>
|
|
</div>
|
|
`;
|
|
},
|
|
|
|
setupProwlarrStatusPolling: function() {
|
|
// Load initial status
|
|
this.loadProwlarrStatus();
|
|
|
|
// Set up polling to refresh Prowlarr status every 30 seconds
|
|
setInterval(() => {
|
|
if (window.huntarrUI && window.huntarrUI.currentSection === 'home') {
|
|
this.loadProwlarrStatus();
|
|
}
|
|
}, 30000);
|
|
},
|
|
|
|
initializeProwlarr: function() {
|
|
console.log('[HuntarrProwlarr] Initializing Prowlarr section');
|
|
|
|
// Load Prowlarr status when section is shown
|
|
this.loadProwlarrStatus();
|
|
|
|
// Any other Prowlarr-specific initialization
|
|
}
|
|
};
|