diff --git a/web/src/assets/logo.png b/web/src/assets/logo.png new file mode 100644 index 0000000..85bf85f Binary files /dev/null and b/web/src/assets/logo.png differ diff --git a/web/src/components/AlertPanel.tsx b/web/src/components/AlertPanel.tsx index b3e2606..005927d 100644 --- a/web/src/components/AlertPanel.tsx +++ b/web/src/components/AlertPanel.tsx @@ -13,6 +13,34 @@ export const alertIdFromHash = (hash: string): string | null => { return match ? decodeURIComponent(match[1]) : null; }; +/** Page numbers around `current`, plus first, last and ellipses. Rendering + * every page button overflowed the card once alert counts reached five + * digits; this keeps the row short whatever the total. */ +export const pageItems = (current: number, total: number): (number | 'ellipsis')[] => { + if (total <= 9) { + return Array.from({ length: total }, (_, index) => index + 1); + } + const wanted = new Set([1, total, current - 1, current, current + 1]); + if (current <= 4) { + [2, 3, 4, 5].forEach((page) => wanted.add(page)); + } + if (current >= total - 3) { + [total - 1, total - 2, total - 3, total - 4].forEach((page) => wanted.add(page)); + } + const items: (number | 'ellipsis')[] = []; + let previous = 0; + for (const page of [...wanted] + .filter((page) => page >= 1 && page <= total) + .sort((a, b) => a - b)) { + if (previous && page - previous > 1) { + items.push('ellipsis'); + } + items.push(page); + previous = page; + } + return items; +}; + const AlertPanel: React.FC = () => { const [alerts, setAlerts] = useState([]); const [stats, setStats] = useState(null); @@ -25,6 +53,10 @@ const AlertPanel: React.FC = () => { const [error, setError] = useState(null); useEffect(() => { + // A new filter shrinks the list; staying on page 500 of what used to be + // 2700 pages shows an empty table behind a pagination row that no longer + // matches. Start over. + setCurrentPage(1); loadAlerts(); loadStats(); setupWebSocket(); @@ -345,15 +377,19 @@ const AlertPanel: React.FC = () => { onClick={() => setCurrentPage(p => Math.max(1, p - 1))} disabled={currentPage === 1} /> - {[...Array(totalPages)].map((_, i) => ( - setCurrentPage(i + 1)} - > - {i + 1} - - ))} + {pageItems(currentPage, totalPages).map((item, index) => + item === 'ellipsis' ? ( + + ) : ( + setCurrentPage(item)} + > + {item} + + ) + )} setCurrentPage(p => Math.min(totalPages, p + 1))} disabled={currentPage === totalPages} diff --git a/web/src/components/Sidebar.css b/web/src/components/Sidebar.css index 0bd26a3..18e6382 100644 --- a/web/src/components/Sidebar.css +++ b/web/src/components/Sidebar.css @@ -21,6 +21,9 @@ width: 39px; height: 39px; object-fit: contain; + flex-shrink: 0; + /* The artwork is a black silhouette; the sidebar is dark, so draw it white. */ + filter: brightness(0) invert(1); } .sidebar-nav { diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index c1be24b..e81a03d 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -1,14 +1,13 @@ import React from 'react'; +import logoUrl from '../assets/logo.png'; import './Sidebar.css'; -const DASHBOARD_LOGO_URL = 'https://github.com/user-attachments/assets/0c8a9216-8315-4ef7-9b73-d96c40521ed1'; - const Sidebar: React.FC = () => { return (