203 lines
9.1 KiB
JavaScript
203 lines
9.1 KiB
JavaScript
// PAGINATION REPLACEMENT CODE FOR dashboard.html
|
|
// Replace lines 1439-1625 with this code
|
|
|
|
// File Quality Analysis with Pagination
|
|
let selectedFiles = new Set();
|
|
let currentAttributeFilter = null; // Track the current attribute filter
|
|
let currentStatusFilter = 'all'; // Track the current status filter
|
|
let currentPage = 1;
|
|
let totalPages = 1;
|
|
let filesPerPage = 100;
|
|
let totalFiles = 0;
|
|
|
|
async function loadFileQuality() {
|
|
const tbody = document.getElementById('qualityTableBody');
|
|
|
|
// Clear selections when reloading (not when paginating)
|
|
// selectedFiles stays intact during pagination
|
|
|
|
try {
|
|
// Calculate offset from current page
|
|
const offset = (currentPage - 1) * filesPerPage;
|
|
|
|
// Build URL with state filter and attribute filter
|
|
let url = `/api/files?limit=${filesPerPage}&offset=${offset}`;
|
|
if (currentStatusFilter !== 'all') {
|
|
url += `&state=${currentStatusFilter}`;
|
|
}
|
|
if (currentAttributeFilter && currentAttributeFilter !== 'all') {
|
|
url += `&filter=${currentAttributeFilter}`;
|
|
}
|
|
|
|
const response = await fetch(url);
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
const files = result.data;
|
|
let html = '';
|
|
|
|
if (files.length === 0 && currentPage === 1) {
|
|
html = '<tr><td colspan="8" style="text-align: center; padding: 40px; color: #64748b;">No files found</td></tr>';
|
|
} else {
|
|
files.forEach(file => {
|
|
const savings = file.original_size && file.encoded_size ?
|
|
((file.original_size - file.encoded_size) / file.original_size * 100).toFixed(1) : '-';
|
|
|
|
const stateBadgeColors = {
|
|
'discovered': '#8b5cf6',
|
|
'pending': '#fbbf24',
|
|
'processing': '#3b82f6',
|
|
'completed': '#10b981',
|
|
'failed': '#ef4444',
|
|
'skipped': '#94a3b8'
|
|
};
|
|
|
|
const statusIcon = file.state === 'completed' ? '✅' :
|
|
file.state === 'failed' ? '❌' :
|
|
file.state === 'processing' ? '⏳' :
|
|
file.state === 'skipped' ? '⏭️' : '⏸️';
|
|
|
|
const disableCheckbox = file.state === 'processing' ? 'disabled' : '';
|
|
|
|
// Create tooltip for state badge
|
|
let stateTooltip = '';
|
|
if (file.state === 'discovered') {
|
|
stateTooltip = 'Discovered - select to encode';
|
|
} else if (file.state === 'pending') {
|
|
stateTooltip = 'Selected - will encode when you click Encode Selected';
|
|
} else if (file.state === 'processing') {
|
|
stateTooltip = 'Currently encoding...';
|
|
} else if (file.state === 'completed' && file.encoder_used) {
|
|
stateTooltip = `Completed with ${file.encoder_used}`;
|
|
} else if (file.state === 'failed' && file.error_message) {
|
|
stateTooltip = `Failed: ${file.error_message}`;
|
|
} else if (file.state === 'skipped' && file.error_message) {
|
|
stateTooltip = `Skipped: ${file.error_message}`;
|
|
}
|
|
|
|
const rowClass = file.state === 'processing' ? 'row-processing' : '';
|
|
|
|
// Escape all user-controlled content
|
|
const escapedPath = escapeHtml(file.relative_path);
|
|
const escapedPathAttr = escapeAttr(file.relative_path);
|
|
const escapedState = escapeHtml(file.state);
|
|
const escapedTooltip = escapeAttr(stateTooltip);
|
|
|
|
// Check if this file is selected
|
|
const isChecked = selectedFiles.has(file.id) ? 'checked' : '';
|
|
|
|
html += `
|
|
<tr class="${rowClass}" data-file-id="${file.id}" style="border-bottom: 1px solid #334155; transition: all 0.2s ease;">
|
|
<td style="padding: 12px; text-align: center;">
|
|
<input type="checkbox" class="file-checkbox" data-file-id="${file.id}"
|
|
onchange="toggleFileSelection(${file.id})"
|
|
${disableCheckbox} ${isChecked}
|
|
style="cursor: ${file.state === 'processing' ? 'not-allowed' : 'pointer'}; transform: scale(1.2);">
|
|
</td>
|
|
<td style="padding: 12px; max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${escapedPathAttr}">
|
|
<span style="font-weight: 500;">${escapedPath}</span>
|
|
</td>
|
|
<td style="padding: 12px;">
|
|
<span style="background: ${stateBadgeColors[file.state] || '#64748b'}; padding: 6px 12px; border-radius: 6px; font-size: 0.85em; cursor: help; font-weight: 600;"
|
|
title="${escapedTooltip}">
|
|
${escapedState}
|
|
</span>
|
|
</td>
|
|
<td style="padding: 12px; color: #94a3b8;">-</td>
|
|
<td style="padding: 12px; font-weight: 500;">${file.original_size ? formatBytes(file.original_size) : '-'}</td>
|
|
<td style="padding: 12px; font-weight: 500;">${file.encoded_size ? formatBytes(file.encoded_size) : '-'}</td>
|
|
<td style="padding: 12px; font-weight: 600; color: ${savings !== '-' && parseFloat(savings) > 0 ? '#10b981' : '#64748b'};">
|
|
${savings !== '-' ? escapeHtml(savings) + '%' : '-'}
|
|
</td>
|
|
<td style="padding: 12px; font-size: 1.2em;">${statusIcon}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
}
|
|
|
|
tbody.innerHTML = html;
|
|
|
|
// Update pagination controls
|
|
// Calculate total pages (estimate based on if we got less than limit)
|
|
if (files.length < filesPerPage) {
|
|
totalPages = currentPage;
|
|
} else {
|
|
// We don't know total, just enable next
|
|
totalPages = currentPage + 1;
|
|
}
|
|
|
|
updatePaginationControls();
|
|
document.getElementById('selectAll').checked = false;
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load file quality:', error);
|
|
tbody.innerHTML = '<tr><td colspan="8" style="text-align: center; padding: 40px; color: #ef4444;">Error loading files</td></tr>';
|
|
}
|
|
}
|
|
|
|
function updatePaginationControls() {
|
|
const paginationContainer = document.getElementById('paginationControls');
|
|
if (!paginationContainer) return;
|
|
|
|
const startFile = (currentPage - 1) * filesPerPage + 1;
|
|
const endFile = currentPage * filesPerPage;
|
|
|
|
let html = `
|
|
<div style="display: flex; align-items: center; justify-content: center; gap: 15px; padding: 20px;">
|
|
<button class="btn" onclick="goToPage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''}
|
|
style="background: #3b82f6; color: white; ${currentPage === 1 ? 'opacity: 0.5; cursor: not-allowed;' : ''}">
|
|
← Previous
|
|
</button>
|
|
|
|
<div style="color: #94a3b8; font-weight: 500;">
|
|
Page ${currentPage} | Showing ${startFile}-${endFile}
|
|
</div>
|
|
|
|
<button class="btn" onclick="goToPage(${currentPage + 1})"
|
|
style="background: #3b82f6; color: white;">
|
|
Next →
|
|
</button>
|
|
|
|
<div style="display: flex; align-items: center; gap: 8px;">
|
|
<label style="color: #94a3b8;">Go to page:</label>
|
|
<input type="number" id="pageInput" min="1" value="${currentPage}"
|
|
style="width: 80px; padding: 8px; border-radius: 6px; background: #1e293b; color: #e2e8f0; border: 1px solid #334155; text-align: center;"
|
|
onkeypress="if(event.key==='Enter') goToPageInput()">
|
|
<button class="btn" onclick="goToPageInput()" style="background: #10b981; color: white;">Go</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
paginationContainer.innerHTML = html;
|
|
}
|
|
|
|
function goToPage(page) {
|
|
if (page < 1) return;
|
|
currentPage = page;
|
|
loadFileQuality();
|
|
}
|
|
|
|
function goToPageInput() {
|
|
const input = document.getElementById('pageInput');
|
|
const page = parseInt(input.value);
|
|
if (page && page > 0) {
|
|
goToPage(page);
|
|
}
|
|
}
|
|
|
|
function changeStatusFilter(status) {
|
|
currentStatusFilter = status;
|
|
currentPage = 1; // Reset to first page
|
|
selectedFiles.clear(); // Clear selections when changing filter
|
|
loadFileQuality();
|
|
}
|
|
|
|
function toggleFileSelection(fileId) {
|
|
if (selectedFiles.has(fileId)) {
|
|
selectedFiles.delete(fileId);
|
|
} else {
|
|
selectedFiles.add(fileId);
|
|
}
|
|
updateSelectedCount();
|
|
}
|