diff --git a/dashboard/index.html b/dashboard/index.html index 894d7dd..9802b24 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -97,6 +97,43 @@

+ + +
totalPages && totalPages > 0) { + currentPage = totalPages; + } + if (currentPage < 1) currentPage = 1; + + if (totalItems === 0) { lockGrid.replaceChildren(emptyState); emptyState.textContent = lockStore.size === 0 ? 'Waiting for lock events from relays\u2026' : 'No locks match the current filters.'; + paginationControls.classList.add('hidden'); return; } - const cards = locks.map((lock) => createLockCard(lock, now)); + // Slice for pagination + const startIdx = (currentPage - 1) * itemsPerPage; + const endIdx = Math.min(startIdx + itemsPerPage, totalItems); + const pageLocks = allLocks.slice(startIdx, endIdx); + + const cards = pageLocks.map((lock) => createLockCard(lock, now)); lockGrid.replaceChildren(...cards); + + // Update Pagination Controls + if (totalItems > itemsPerPage) { + paginationControls.classList.remove('hidden'); + + // Update text + pageStartEl.textContent = startIdx + 1; + pageEndEl.textContent = endIdx; + totalItemsEl.textContent = totalItems; + + // Update buttons state + const isFirst = currentPage === 1; + const isLast = currentPage === totalPages; + + prevBtn.disabled = isFirst; + nextBtn.disabled = isLast; + mobilePrevBtn.disabled = isFirst; + mobileNextBtn.disabled = isLast; + } else { + paginationControls.classList.add('hidden'); + } } // ----------------------------------------------------------------------- @@ -704,8 +790,19 @@ // ----------------------------------------------------------------------- // Event handlers // ----------------------------------------------------------------------- - if (cadenceFilter) cadenceFilter.addEventListener('change', renderLocks); - if (statusFilter) statusFilter.addEventListener('change', renderLocks); + function setPage(p) { + currentPage = p; + renderLocks(); + } + + if (cadenceFilter) cadenceFilter.addEventListener('change', () => { currentPage = 1; renderLocks(); }); + if (statusFilter) statusFilter.addEventListener('change', () => { currentPage = 1; renderLocks(); }); + + if (prevBtn) prevBtn.addEventListener('click', () => setPage(currentPage - 1)); + if (nextBtn) nextBtn.addEventListener('click', () => setPage(currentPage + 1)); + if (mobilePrevBtn) mobilePrevBtn.addEventListener('click', () => setPage(currentPage - 1)); + if (mobileNextBtn) mobileNextBtn.addEventListener('click', () => setPage(currentPage + 1)); + if (refreshBtn) refreshBtn.addEventListener('click', () => { // Close existing connections and reconnect for (const ws of sockets) { diff --git a/dashboard/styles.css b/dashboard/styles.css index ab28511..23a310f 100644 --- a/dashboard/styles.css +++ b/dashboard/styles.css @@ -178,3 +178,60 @@ a { color: inherit; } @media (min-width: 1024px) { .lg\:px-8 { padding-left: 2rem; padding-right: 2rem; } } + +/* Pagination Utilities */ +.hidden { display: none; } +.block { display: block; } +.relative { position: relative; } +.justify-center { justify-content: center; } + +.pagination-nav { + display: inline-flex; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + border-radius: 0.375rem; +} + +.pagination-btn { + position: relative; + display: inline-flex; + align-items: center; + padding: 0.5rem 0.75rem; + color: var(--muted); + background-color: transparent; + border: 1px solid var(--border); + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: background-color 0.15s ease; +} + +.pagination-btn:hover:not(:disabled) { + background-color: var(--surface-alt); +} + +.pagination-btn:first-child { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +.pagination-btn:last-child { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; +} + +.pagination-btn + .pagination-btn { + margin-left: -1px; +} + +.pagination-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +@media (min-width: 640px) { + .sm\:hidden { display: none; } + .sm\:flex { display: flex; } + .sm\:flex-1 { flex: 1 1 0%; } + .sm\:items-center { align-items: center; } + .sm\:justify-between { justify-content: space-between; } +} diff --git a/src/dashboard.mjs b/src/dashboard.mjs index 247c16b..3474029 100644 --- a/src/dashboard.mjs +++ b/src/dashboard.mjs @@ -68,6 +68,7 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT) { switch (extname) { case '.html': contentType = 'text/html'; break; case '.js': contentType = 'text/javascript'; break; + case '.mjs': contentType = 'text/javascript'; break; case '.css': contentType = 'text/css'; break; case '.json': contentType = 'application/json'; break; case '.png': contentType = 'image/png'; break;