Skip to content

Commit c344981

Browse files
committed
Implement pagination and app switching enhancements in LogsModule
- Introduced pagination state management with current page, total pages, and page size. - Updated LogsModule to handle pagination UI updates and navigation events. - Enhanced app switching functionality to reset pagination when changing apps. - Removed redundant pagination logic from the HTML template, allowing LogsModule to manage pagination directly. - Improved logging for better debugging and tracking of pagination and app changes.
1 parent 792cde8 commit c344981

File tree

3 files changed

+169
-415
lines changed

3 files changed

+169
-415
lines changed

frontend/static/js/logs.js

Lines changed: 160 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ window.LogsModule = {
1414
autoScrollWasEnabled: false,
1515
userTimezone: null, // Cache for user's timezone setting
1616

17+
// Pagination state
18+
currentPage: 1,
19+
totalPages: 1,
20+
pageSize: 20,
21+
totalLogs: 0,
22+
1723
// Element references
1824
elements: {},
1925

@@ -23,6 +29,11 @@ window.LogsModule = {
2329
this.cacheElements();
2430
this.loadUserTimezone();
2531
this.setupEventListeners();
32+
33+
// Load initial logs for the default app without resetting pagination
34+
console.log('[LogsModule] Loading initial logs...');
35+
this.loadLogsFromAPI(this.currentLogApp);
36+
this.setupLogPolling(this.currentLogApp);
2637
},
2738

2839
// Load user's timezone setting from the backend
@@ -141,6 +152,13 @@ window.LogsModule = {
141152
this.elements.currentLogApp = document.getElementById('current-log-app');
142153
this.elements.logDropdownBtn = document.querySelector('.log-dropdown-btn');
143154
this.elements.logDropdownContent = document.querySelector('.log-dropdown-content');
155+
156+
// Pagination elements
157+
this.elements.logsPrevPage = document.getElementById('logsPrevPage');
158+
this.elements.logsNextPage = document.getElementById('logsNextPage');
159+
this.elements.logsCurrentPage = document.getElementById('logsCurrentPage');
160+
this.elements.logsTotalPages = document.getElementById('logsTotalPages');
161+
this.elements.logsPageSize = document.getElementById('logsPageSize');
144162
},
145163

146164
// Set up event listeners for logging functionality
@@ -219,6 +237,19 @@ window.LogsModule = {
219237
this.handleLogOptionChange(app);
220238
});
221239
}
240+
241+
// Pagination event listeners
242+
if (this.elements.logsPrevPage) {
243+
this.elements.logsPrevPage.addEventListener('click', () => this.handlePagination('prev'));
244+
}
245+
246+
if (this.elements.logsNextPage) {
247+
this.elements.logsNextPage.addEventListener('click', () => this.handlePagination('next'));
248+
}
249+
250+
if (this.elements.logsPageSize) {
251+
this.elements.logsPageSize.addEventListener('change', () => this.handlePageSizeChange());
252+
}
222253
},
223254

224255
// Handle log option dropdown changes
@@ -228,7 +259,12 @@ window.LogsModule = {
228259
} else if (app && app.target && typeof app.target.getAttribute === 'function') {
229260
app = app.target.getAttribute('data-app');
230261
}
231-
if (!app || app === this.currentLogApp) return;
262+
if (!app || app === this.currentLogApp) {
263+
console.log(`[LogsModule] handleLogOptionChange - no change needed (${app} === ${this.currentLogApp})`);
264+
return;
265+
}
266+
267+
console.log(`[LogsModule] handleLogOptionChange - switching from ${this.currentLogApp} to ${app}`);
232268

233269
// Update the select value
234270
const logAppSelect = document.getElementById('logAppSelect');
@@ -243,12 +279,43 @@ window.LogsModule = {
243279

244280
// Switch to the selected app logs
245281
this.currentLogApp = app;
282+
this.currentPage = 1; // Reset to first page when switching apps
283+
this.clearLogs();
284+
this.connectToLogs();
285+
},
286+
287+
// Handle app changes from external sources (like huntarrUI tab switching)
288+
handleAppChange: function(app) {
289+
if (!app || app === this.currentLogApp) {
290+
console.log(`[LogsModule] handleAppChange - no change needed (${app} === ${this.currentLogApp})`);
291+
return;
292+
}
293+
294+
console.log(`[LogsModule] handleAppChange - switching from ${this.currentLogApp} to ${app}`);
295+
296+
// Update the select value
297+
const logAppSelect = document.getElementById('logAppSelect');
298+
if (logAppSelect) logAppSelect.value = app;
299+
300+
// Update the current log app text with proper capitalization
301+
let displayName = app.charAt(0).toUpperCase() + app.slice(1);
302+
if (app === 'whisparr') displayName = 'Whisparr V2';
303+
else if (app === 'eros') displayName = 'Whisparr V3';
304+
305+
if (this.elements.currentLogApp) this.elements.currentLogApp.textContent = displayName;
306+
307+
// Switch to the selected app logs
308+
this.currentLogApp = app;
309+
this.currentPage = 1; // Reset to first page when switching apps
246310
this.clearLogs();
247311
this.connectToLogs();
248312
},
249313

250314
// Connect to logs stream
251315
connectToLogs: function() {
316+
console.log(`[LogsModule] connectToLogs() called - currentLogApp: ${this.currentLogApp}, currentPage: ${this.currentPage}`);
317+
console.trace('[LogsModule] connectToLogs call stack');
318+
252319
// Disconnect any existing event sources
253320
this.disconnectAllEventSources();
254321

@@ -273,7 +340,8 @@ window.LogsModule = {
273340
this.elements.logConnectionStatus.className = '';
274341
}
275342

276-
// Load initial logs
343+
// Load logs for the current page (don't always reset to page 1)
344+
console.log(`[LogsModule] connectEventSource - loading page ${this.currentPage} for app ${appType}`);
277345
this.loadLogsFromAPI(appType);
278346

279347
// Set up polling with user's configured interval
@@ -298,14 +366,20 @@ window.LogsModule = {
298366

299367
// Set up polling for new logs using the configured interval
300368
this.logPollingInterval = setInterval(() => {
301-
this.loadLogsFromAPI(appType, true);
369+
// Only poll for new logs when on page 1 (latest logs)
370+
if (this.currentPage === 1) {
371+
this.loadLogsFromAPI(appType, true);
372+
}
302373
}, intervalMs);
303374
})
304375
.catch(error => {
305376
console.error('[LogsModule] Error fetching log refresh interval, using default 30 seconds:', error);
306377
// Fallback to 30 seconds if settings fetch fails
307378
this.logPollingInterval = setInterval(() => {
308-
this.loadLogsFromAPI(appType, true);
379+
// Only poll for new logs when on page 1 (latest logs)
380+
if (this.currentPage === 1) {
381+
this.loadLogsFromAPI(appType, true);
382+
}
309383
}, 30000);
310384
});
311385
},
@@ -315,11 +389,20 @@ window.LogsModule = {
315389
// Use the correct API endpoint - the backend now supports 'all' as an app_type
316390
const apiUrl = `/api/logs/${appType}`;
317391

318-
const limit = isPolling ? 20 : 100; // Load fewer logs when polling for updates
392+
// For polling, always get latest logs (offset=0, small limit)
393+
// For pagination, use current page and page size
394+
let limit, offset;
395+
if (isPolling) {
396+
limit = 20;
397+
offset = 0;
398+
} else {
399+
limit = this.pageSize;
400+
offset = (this.currentPage - 1) * this.pageSize;
401+
}
319402

320403
// Include level filter in API call if a specific level is selected
321404
const currentLogLevel = this.elements.logLevelSelect ? this.elements.logLevelSelect.value : 'all';
322-
let apiParams = `limit=${limit}&offset=0`;
405+
let apiParams = `limit=${limit}&offset=${offset}`;
323406
if (currentLogLevel !== 'all') {
324407
apiParams += `&level=${currentLogLevel.toUpperCase()}`;
325408
}
@@ -331,6 +414,17 @@ window.LogsModule = {
331414
.then(data => {
332415
if (data.success && data.logs) {
333416
this.processLogsFromAPI(data.logs, appType, isPolling);
417+
418+
// Update pagination info (only on non-polling requests)
419+
if (!isPolling && data.total !== undefined) {
420+
this.totalLogs = data.total;
421+
this.totalPages = Math.max(1, Math.ceil(this.totalLogs / this.pageSize));
422+
console.log(`[LogsModule] Updated pagination: totalLogs=${this.totalLogs}, totalPages=${this.totalPages}, currentPage=${this.currentPage}`);
423+
this.updatePaginationUI();
424+
} else if (isPolling) {
425+
console.log(`[LogsModule] Polling request - not updating pagination. Current: totalLogs=${this.totalLogs}, totalPages=${this.totalPages}, currentPage=${this.currentPage}`);
426+
}
427+
334428
// Update connection status on successful API call (only on initial load, not polling)
335429
if (this.elements.logConnectionStatus && !isPolling) {
336430
this.elements.logConnectionStatus.textContent = 'Connected';
@@ -727,86 +821,13 @@ window.LogsModule = {
727821

728822
// Filter logs by level
729823
filterLogsByLevel: function(selectedLevel) {
730-
if (!this.elements.logsContainer) return;
731-
732-
const allLogEntries = this.elements.logsContainer.querySelectorAll('.log-entry');
733-
let visibleCount = 0;
734-
let totalCount = allLogEntries.length;
735-
736-
console.log(`[LogsModule] Filtering logs by level: ${selectedLevel}, total entries: ${totalCount}`);
824+
console.log(`[LogsModule] Filtering logs by level: ${selectedLevel}`);
737825

738-
allLogEntries.forEach(entry => {
739-
entry.removeAttribute('data-hidden-by-filter');
740-
});
826+
// Reset to first page when changing filter
827+
this.currentPage = 1;
741828

742-
allLogEntries.forEach(entry => {
743-
if (selectedLevel === 'all') {
744-
entry.style.display = '';
745-
visibleCount++;
746-
} else {
747-
const levelBadge = entry.querySelector('.log-level-badge, .log-level, .log-level-error, .log-level-warning, .log-level-info, .log-level-debug');
748-
749-
if (levelBadge) {
750-
let entryLevel = '';
751-
const badgeText = levelBadge.textContent.toLowerCase().trim();
752-
753-
switch(badgeText) {
754-
case 'information':
755-
case 'info':
756-
entryLevel = 'info';
757-
break;
758-
case 'warning':
759-
case 'warn':
760-
entryLevel = 'warning';
761-
break;
762-
case 'error':
763-
entryLevel = 'error';
764-
break;
765-
case 'debug':
766-
entryLevel = 'debug';
767-
break;
768-
case 'fatal':
769-
case 'critical':
770-
entryLevel = 'error';
771-
break;
772-
default:
773-
if (levelBadge.classList.contains('log-level-error')) {
774-
entryLevel = 'error';
775-
} else if (levelBadge.classList.contains('log-level-warning')) {
776-
entryLevel = 'warning';
777-
} else if (levelBadge.classList.contains('log-level-info')) {
778-
entryLevel = 'info';
779-
} else if (levelBadge.classList.contains('log-level-debug')) {
780-
entryLevel = 'debug';
781-
} else {
782-
entryLevel = null;
783-
}
784-
}
785-
786-
if (entryLevel && entryLevel === selectedLevel) {
787-
entry.style.display = '';
788-
visibleCount++;
789-
} else {
790-
entry.style.display = 'none';
791-
entry.setAttribute('data-hidden-by-filter', 'true');
792-
}
793-
} else {
794-
entry.style.display = 'none';
795-
entry.setAttribute('data-hidden-by-filter', 'true');
796-
}
797-
}
798-
});
799-
800-
if (this.autoScroll && this.elements.autoScrollCheckbox && this.elements.autoScrollCheckbox.checked && visibleCount > 0) {
801-
setTimeout(() => {
802-
window.scrollTo({
803-
top: 0,
804-
behavior: 'smooth'
805-
});
806-
}, 100);
807-
}
808-
809-
console.log(`[LogsModule] Filtered logs by level '${selectedLevel}': showing ${visibleCount}/${totalCount} entries`);
829+
// Reload logs from API with new filter
830+
this.loadLogsFromAPI(this.currentLogApp, false);
810831
},
811832

812833
// Apply filter to single entry
@@ -918,9 +939,64 @@ window.LogsModule = {
918939
return false;
919940
},
920941

942+
// Handle pagination navigation
943+
handlePagination: function(direction) {
944+
if (direction === 'prev' && this.currentPage > 1) {
945+
this.currentPage--;
946+
this.loadLogsFromAPI(this.currentLogApp, false);
947+
} else if (direction === 'next' && this.currentPage < this.totalPages) {
948+
this.currentPage++;
949+
this.loadLogsFromAPI(this.currentLogApp, false);
950+
}
951+
},
952+
953+
// Handle page size change
954+
handlePageSizeChange: function() {
955+
const newPageSize = parseInt(this.elements.logsPageSize.value);
956+
if (newPageSize !== this.pageSize) {
957+
this.pageSize = newPageSize;
958+
this.currentPage = 1; // Reset to first page
959+
this.loadLogsFromAPI(this.currentLogApp, false);
960+
}
961+
},
962+
963+
// Update pagination UI elements
964+
updatePaginationUI: function() {
965+
console.log(`[LogsModule] updatePaginationUI called - currentPage: ${this.currentPage}, totalPages: ${this.totalPages}`);
966+
console.log(`[LogsModule] DOM elements found:`, {
967+
logsCurrentPage: !!this.elements.logsCurrentPage,
968+
logsTotalPages: !!this.elements.logsTotalPages,
969+
logsPrevPage: !!this.elements.logsPrevPage,
970+
logsNextPage: !!this.elements.logsNextPage
971+
});
972+
973+
if (this.elements.logsCurrentPage) {
974+
this.elements.logsCurrentPage.textContent = this.currentPage;
975+
console.log(`[LogsModule] Updated logsCurrentPage to: ${this.currentPage}`);
976+
} else {
977+
console.warn('[LogsModule] logsCurrentPage element not found!');
978+
}
979+
980+
if (this.elements.logsTotalPages) {
981+
this.elements.logsTotalPages.textContent = this.totalPages;
982+
console.log(`[LogsModule] Updated logsTotalPages to: ${this.totalPages}`);
983+
} else {
984+
console.warn('[LogsModule] logsTotalPages element not found!');
985+
}
986+
987+
if (this.elements.logsPrevPage) {
988+
this.elements.logsPrevPage.disabled = this.currentPage <= 1;
989+
}
990+
991+
if (this.elements.logsNextPage) {
992+
this.elements.logsNextPage.disabled = this.currentPage >= this.totalPages;
993+
}
994+
},
995+
921996
// Reset logs to default state
922997
resetToDefaults: function() {
923998
this.currentLogApp = 'all';
999+
this.currentPage = 1; // Reset pagination
9241000

9251001
const logAppSelect = document.getElementById('logAppSelect');
9261002
if (logAppSelect && logAppSelect.value !== 'all') {

0 commit comments

Comments
 (0)