Fix history pagination and status panel issues
Build and Push Docker Image / build (push) Successful in 52s
Docs Check / Markdown lint (push) Successful in 1m1s
CI / Tests & coverage (push) Failing after 1m32s
CI / Security audit (push) Successful in 1m33s
Docs Check / Mermaid diagram parse check (push) Successful in 1m38s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 56s
Build and Push Docker Image / build (push) Successful in 52s
Docs Check / Markdown lint (push) Successful in 1m1s
CI / Tests & coverage (push) Failing after 1m32s
CI / Security audit (push) Successful in 1m33s
Docs Check / Mermaid diagram parse check (push) Successful in 1m38s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 56s
- Fix history pagination: use retriever's built-in maxPages parameter instead of broken date-based cursor - Fix status panel: correct API endpoint from /api/dashboard/status to /api/status - Background fetch now properly fetches up to 1000 records (10 pages * 100 records) - Status panel will now display details instead of 'Loading Status...'
This commit is contained in:
+1
-1
@@ -281,7 +281,7 @@ export async function testRadarrWebhook() {
|
|||||||
|
|
||||||
export async function refreshStatusPanel() {
|
export async function refreshStatusPanel() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/dashboard/status');
|
const res = await fetch('/api/status');
|
||||||
if (!res.ok) throw new Error('Failed to fetch status: ' + res.status);
|
if (!res.ok) throw new Error('Failed to fetch status: ' + res.status);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
return { success: true, data };
|
return { success: true, data };
|
||||||
|
|||||||
+78
-152
@@ -10,7 +10,7 @@ const HISTORY_CACHE_TTL = 5 * 60 * 1000;
|
|||||||
// Staged loading configuration
|
// Staged loading configuration
|
||||||
const INITIAL_PAGE_SIZE = 100;
|
const INITIAL_PAGE_SIZE = 100;
|
||||||
const MAX_TOTAL_RECORDS = 1000;
|
const MAX_TOTAL_RECORDS = 1000;
|
||||||
const BATCH_PAGE_SIZE = 100;
|
const MAX_PAGES = 10; // 10 pages * 100 records = 1000 total
|
||||||
|
|
||||||
// Background fetch state to prevent concurrent fetches
|
// Background fetch state to prevent concurrent fetches
|
||||||
const backgroundFetchState = {
|
const backgroundFetchState = {
|
||||||
@@ -61,6 +61,7 @@ async function fetchSonarrHistory(since) {
|
|||||||
try {
|
try {
|
||||||
const response = await retriever.getHistory({
|
const response = await retriever.getHistory({
|
||||||
pageSize: INITIAL_PAGE_SIZE,
|
pageSize: INITIAL_PAGE_SIZE,
|
||||||
|
maxPages: 1,
|
||||||
sortKey: 'date',
|
sortKey: 'date',
|
||||||
sortDir: 'descending',
|
sortDir: 'descending',
|
||||||
includeSeries: true,
|
includeSeries: true,
|
||||||
@@ -92,7 +93,7 @@ async function fetchSonarrHistory(since) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger background fetch for remaining Sonarr history records.
|
* Trigger background fetch for remaining Sonarr history records.
|
||||||
* Fetches in batches of 100 up to 1000 total records using date-based cursor pagination.
|
* Uses the retriever's built-in pagination to fetch up to 1000 records.
|
||||||
*/
|
*/
|
||||||
async function triggerBackgroundSonarrFetch(since) {
|
async function triggerBackgroundSonarrFetch(since) {
|
||||||
if (backgroundFetchState.sonarr.inProgress) return;
|
if (backgroundFetchState.sonarr.inProgress) return;
|
||||||
@@ -109,85 +110,44 @@ async function triggerBackgroundSonarrFetch(since) {
|
|||||||
const instances = getSonarrInstances();
|
const instances = getSonarrInstances();
|
||||||
const sonarrRetrievers = arrRetrieverRegistry.getRetrieversByType('sonarr');
|
const sonarrRetrievers = arrRetrieverRegistry.getRetrieversByType('sonarr');
|
||||||
|
|
||||||
let allRecords = cache.get('history:sonarr') || [];
|
// Fetch all records up to MAX_PAGES using built-in pagination
|
||||||
const recordIds = new Set(allRecords.map(r => r.id));
|
const results = await Promise.all(sonarrRetrievers.map(async (retriever) => {
|
||||||
let cursorDate = null;
|
const inst = instances.find(i => i.id === retriever.getInstanceId());
|
||||||
|
if (!inst) return [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await retriever.getHistory({
|
||||||
|
pageSize: INITIAL_PAGE_SIZE,
|
||||||
|
maxPages: MAX_PAGES,
|
||||||
|
sortKey: 'date',
|
||||||
|
sortDir: 'descending',
|
||||||
|
includeSeries: true,
|
||||||
|
includeEpisode: true,
|
||||||
|
startDate: since.toISOString()
|
||||||
|
});
|
||||||
|
const records = (response && response.records) || [];
|
||||||
|
return records.map(r => {
|
||||||
|
if (r.series) r.series._instanceUrl = inst.url;
|
||||||
|
if (r.series) r.series._instanceName = inst.name || inst.id;
|
||||||
|
r._instanceUrl = inst.url;
|
||||||
|
r._instanceName = inst.name || inst.id;
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[HistoryFetcher] Sonarr background fetch ${inst.id} error:`, err.message);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const allRecords = results.flat();
|
||||||
|
|
||||||
// Get the oldest date from current records as cursor
|
// Update cache with all records
|
||||||
if (allRecords.length > 0) {
|
cache.set('history:sonarr', allRecords, HISTORY_CACHE_TTL);
|
||||||
const dates = allRecords.map(r => new Date(r.date)).filter(d => !isNaN(d));
|
|
||||||
if (dates.length > 0) {
|
// Emit SSE event for history update
|
||||||
cursorDate = new Date(Math.min(...dates));
|
emitHistoryUpdate('sonarr');
|
||||||
}
|
|
||||||
}
|
console.log(`[HistoryFetcher] Background fetch complete: ${allRecords.length} Sonarr records`);
|
||||||
|
|
||||||
// Fetch additional batches until we have 1000 records or no more available
|
|
||||||
while (allRecords.length < MAX_TOTAL_RECORDS) {
|
|
||||||
const batchSize = Math.min(BATCH_PAGE_SIZE, MAX_TOTAL_RECORDS - allRecords.length);
|
|
||||||
|
|
||||||
const batchResults = await Promise.all(sonarrRetrievers.map(async (retriever) => {
|
|
||||||
const inst = instances.find(i => i.id === retriever.getInstanceId());
|
|
||||||
if (!inst) return [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const params = {
|
|
||||||
pageSize: batchSize,
|
|
||||||
sortKey: 'date',
|
|
||||||
sortDir: 'descending',
|
|
||||||
includeSeries: true,
|
|
||||||
includeEpisode: true,
|
|
||||||
startDate: since.toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use date-based cursor if available
|
|
||||||
if (cursorDate) {
|
|
||||||
params.sortKey = 'date';
|
|
||||||
params.sortDir = 'descending';
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await retriever.getHistory(params);
|
|
||||||
const records = (response && response.records) || [];
|
|
||||||
|
|
||||||
// Filter out records we already have (by ID) to prevent duplicates
|
|
||||||
const newRecords = records.filter(r => !recordIds.has(r.id));
|
|
||||||
|
|
||||||
return newRecords.map(r => {
|
|
||||||
if (r.series) r.series._instanceUrl = inst.url;
|
|
||||||
if (r.series) r.series._instanceName = inst.name || inst.id;
|
|
||||||
r._instanceUrl = inst.url;
|
|
||||||
r._instanceName = inst.name || inst.id;
|
|
||||||
return r;
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`[HistoryFetcher] Sonarr background fetch ${inst.id} error:`, err.message);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const batchFlat = batchResults.flat();
|
|
||||||
|
|
||||||
// If no new records, we've reached the end
|
|
||||||
if (batchFlat.length === 0) break;
|
|
||||||
|
|
||||||
// Add new records to our collection
|
|
||||||
batchFlat.forEach(r => recordIds.add(r.id));
|
|
||||||
allRecords = allRecords.concat(batchFlat);
|
|
||||||
|
|
||||||
// Update cache with new records
|
|
||||||
cache.set('history:sonarr', allRecords, HISTORY_CACHE_TTL);
|
|
||||||
|
|
||||||
// Update cursor date to the oldest record in this batch
|
|
||||||
const batchDates = batchFlat.map(r => new Date(r.date)).filter(d => !isNaN(d));
|
|
||||||
if (batchDates.length > 0) {
|
|
||||||
cursorDate = new Date(Math.min(...batchDates));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit SSE event for history update
|
|
||||||
emitHistoryUpdate('sonarr');
|
|
||||||
|
|
||||||
// Small delay between batches to avoid overwhelming the API
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[HistoryFetcher] Background Sonarr fetch error:', err.message);
|
console.error('[HistoryFetcher] Background Sonarr fetch error:', err.message);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -227,6 +187,7 @@ async function fetchRadarrHistory(since) {
|
|||||||
try {
|
try {
|
||||||
const response = await retriever.getHistory({
|
const response = await retriever.getHistory({
|
||||||
pageSize: INITIAL_PAGE_SIZE,
|
pageSize: INITIAL_PAGE_SIZE,
|
||||||
|
maxPages: 1,
|
||||||
sortKey: 'date',
|
sortKey: 'date',
|
||||||
sortDir: 'descending',
|
sortDir: 'descending',
|
||||||
includeMovie: true,
|
includeMovie: true,
|
||||||
@@ -257,7 +218,7 @@ async function fetchRadarrHistory(since) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger background fetch for remaining Radarr history records.
|
* Trigger background fetch for remaining Radarr history records.
|
||||||
* Fetches in batches of 100 up to 1000 total records using date-based cursor pagination.
|
* Uses the retriever's built-in pagination to fetch up to 1000 records.
|
||||||
*/
|
*/
|
||||||
async function triggerBackgroundRadarrFetch(since) {
|
async function triggerBackgroundRadarrFetch(since) {
|
||||||
if (backgroundFetchState.radarr.inProgress) return;
|
if (backgroundFetchState.radarr.inProgress) return;
|
||||||
@@ -274,78 +235,43 @@ async function triggerBackgroundRadarrFetch(since) {
|
|||||||
const instances = getRadarrInstances();
|
const instances = getRadarrInstances();
|
||||||
const radarrRetrievers = arrRetrieverRegistry.getRetrieversByType('radarr');
|
const radarrRetrievers = arrRetrieverRegistry.getRetrieversByType('radarr');
|
||||||
|
|
||||||
let allRecords = cache.get('history:radarr') || [];
|
// Fetch all records up to MAX_PAGES using built-in pagination
|
||||||
const recordIds = new Set(allRecords.map(r => r.id));
|
const results = await Promise.all(radarrRetrievers.map(async (retriever) => {
|
||||||
let cursorDate = null;
|
const inst = instances.find(i => i.id === retriever.getInstanceId());
|
||||||
|
if (!inst) return [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await retriever.getHistory({
|
||||||
|
pageSize: INITIAL_PAGE_SIZE,
|
||||||
|
maxPages: MAX_PAGES,
|
||||||
|
sortKey: 'date',
|
||||||
|
sortDir: 'descending',
|
||||||
|
includeMovie: true,
|
||||||
|
startDate: since.toISOString()
|
||||||
|
});
|
||||||
|
const records = (response && response.records) || [];
|
||||||
|
return records.map(r => {
|
||||||
|
if (r.movie) r.movie._instanceUrl = inst.url;
|
||||||
|
if (r.movie) r.movie._instanceName = inst.name || inst.id;
|
||||||
|
r._instanceUrl = inst.url;
|
||||||
|
r._instanceName = inst.name || inst.id;
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[HistoryFetcher] Radarr background fetch ${inst.id} error:`, err.message);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const allRecords = results.flat();
|
||||||
|
|
||||||
// Get the oldest date from current records as cursor
|
// Update cache with all records
|
||||||
if (allRecords.length > 0) {
|
cache.set('history:radarr', allRecords, HISTORY_CACHE_TTL);
|
||||||
const dates = allRecords.map(r => new Date(r.date)).filter(d => !isNaN(d));
|
|
||||||
if (dates.length > 0) {
|
// Emit SSE event for history update
|
||||||
cursorDate = new Date(Math.min(...dates));
|
emitHistoryUpdate('radarr');
|
||||||
}
|
|
||||||
}
|
console.log(`[HistoryFetcher] Background fetch complete: ${allRecords.length} Radarr records`);
|
||||||
|
|
||||||
// Fetch additional batches until we have 1000 records or no more available
|
|
||||||
while (allRecords.length < MAX_TOTAL_RECORDS) {
|
|
||||||
const batchSize = Math.min(BATCH_PAGE_SIZE, MAX_TOTAL_RECORDS - allRecords.length);
|
|
||||||
|
|
||||||
const batchResults = await Promise.all(radarrRetrievers.map(async (retriever) => {
|
|
||||||
const inst = instances.find(i => i.id === retriever.getInstanceId());
|
|
||||||
if (!inst) return [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const params = {
|
|
||||||
pageSize: batchSize,
|
|
||||||
sortKey: 'date',
|
|
||||||
sortDir: 'descending',
|
|
||||||
includeMovie: true,
|
|
||||||
startDate: since.toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await retriever.getHistory(params);
|
|
||||||
const records = (response && response.records) || [];
|
|
||||||
|
|
||||||
// Filter out records we already have (by ID) to prevent duplicates
|
|
||||||
const newRecords = records.filter(r => !recordIds.has(r.id));
|
|
||||||
|
|
||||||
return newRecords.map(r => {
|
|
||||||
if (r.movie) r.movie._instanceUrl = inst.url;
|
|
||||||
if (r.movie) r.movie._instanceName = inst.name || inst.id;
|
|
||||||
r._instanceUrl = inst.url;
|
|
||||||
r._instanceName = inst.name || inst.id;
|
|
||||||
return r;
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`[HistoryFetcher] Radarr background fetch ${inst.id} error:`, err.message);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const batchFlat = batchResults.flat();
|
|
||||||
|
|
||||||
// If no new records, we've reached the end
|
|
||||||
if (batchFlat.length === 0) break;
|
|
||||||
|
|
||||||
// Add new records to our collection
|
|
||||||
batchFlat.forEach(r => recordIds.add(r.id));
|
|
||||||
allRecords = allRecords.concat(batchFlat);
|
|
||||||
|
|
||||||
// Update cache with new records
|
|
||||||
cache.set('history:radarr', allRecords, HISTORY_CACHE_TTL);
|
|
||||||
|
|
||||||
// Update cursor date to the oldest record in this batch
|
|
||||||
const batchDates = batchFlat.map(r => new Date(r.date)).filter(d => !isNaN(d));
|
|
||||||
if (batchDates.length > 0) {
|
|
||||||
cursorDate = new Date(Math.min(...batchDates));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit SSE event for history update
|
|
||||||
emitHistoryUpdate('radarr');
|
|
||||||
|
|
||||||
// Small delay between batches to avoid overwhelming the API
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[HistoryFetcher] Background Radarr fetch error:', err.message);
|
console.error('[HistoryFetcher] Background Radarr fetch error:', err.message);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user