@@ -1804,6 +1804,102 @@ def get_log_css():
18041804 background-color: #4CAF50;
18051805 color: white;
18061806}
1807+
1808+ /* Log search container styles */
1809+ .log-search-container {
1810+ display: flex;
1811+ align-items: center;
1812+ margin-bottom: 10px;
1813+ padding: 8px;
1814+ background-color: #f8f9fa;
1815+ border: 1px solid #ddd;
1816+ border-radius: 4px;
1817+ gap: 8px;
1818+ }
1819+
1820+ .log-search-input {
1821+ flex-grow: 1;
1822+ padding: 6px 10px;
1823+ border: 1px solid #ccc;
1824+ border-radius: 4px;
1825+ font-size: 14px;
1826+ min-width: 200px;
1827+ }
1828+
1829+ .log-search-input:focus {
1830+ outline: none;
1831+ border-color: #4CAF50;
1832+ box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
1833+ }
1834+
1835+ .clear-search-button {
1836+ padding: 6px 12px;
1837+ background-color: #6c757d;
1838+ color: white;
1839+ border: none;
1840+ border-radius: 4px;
1841+ cursor: pointer;
1842+ font-size: 14px;
1843+ white-space: nowrap;
1844+ }
1845+
1846+ .clear-search-button:hover {
1847+ background-color: #5a6268;
1848+ }
1849+
1850+ .search-status {
1851+ font-size: 12px;
1852+ color: #6c757d;
1853+ white-space: nowrap;
1854+ }
1855+
1856+ /* Dark mode search styles */
1857+ body.dark-mode .log-search-container {
1858+ background-color: #2d3748;
1859+ border-color: #4a5568;
1860+ }
1861+
1862+ body.dark-mode .log-search-input {
1863+ background-color: #4a5568;
1864+ border-color: #718096;
1865+ color: #fff;
1866+ }
1867+
1868+ body.dark-mode .log-search-input:focus {
1869+ border-color: #4CAF50;
1870+ }
1871+
1872+ body.dark-mode .log-search-input::placeholder {
1873+ color: #a0aec0;
1874+ }
1875+
1876+ body.dark-mode .clear-search-button {
1877+ background-color: #4a5568;
1878+ }
1879+
1880+ body.dark-mode .clear-search-button:hover {
1881+ background-color: #2d3748;
1882+ }
1883+
1884+ body.dark-mode .search-status {
1885+ color: #a0aec0;
1886+ }
1887+
1888+ /* Hide filtered log entries */
1889+ .log-entry-hidden {
1890+ display: none !important;
1891+ }
1892+
1893+ /* Highlight matching text */
1894+ .search-highlight {
1895+ background-color: #ffeb3b;
1896+ font-weight: bold;
1897+ }
1898+
1899+ body.dark-mode .search-highlight {
1900+ background-color: #ffa000;
1901+ color: #000;
1902+ }
18071903</style>
18081904"""
18091905
@@ -2262,6 +2358,15 @@ def get_logfile_js(filter_type):
22622358 }}
22632359 }});
22642360
2361+ // Apply current search filter to new entries
2362+ const searchTerm = document.getElementById('logSearchInput').value.toLowerCase();
2363+ if (searchTerm.trim() !== '') {{
2364+ // Re-apply filter to include new entries
2365+ setTimeout(() => {{
2366+ filterLogEntries();
2367+ }}, 10);
2368+ }}
2369+
22652370 // Auto-scroll to top for new entries (since newest are at top)
22662371 if (newEntriesAdded > 0 && shouldAutoScroll) {{
22672372 setTimeout(() => {{
@@ -2283,6 +2388,73 @@ def get_logfile_js(filter_type):
22832388 }});
22842389 }}
22852390
2391+ // Search functionality
2392+ function filterLogEntries() {{
2393+ const searchTerm = document.getElementById('logSearchInput').value.toLowerCase();
2394+ const rows = document.querySelectorAll('#logTableBody tr[data-line]');
2395+ const statusDiv = document.getElementById('searchStatus');
2396+
2397+ let visibleCount = 0;
2398+ let totalCount = rows.length;
2399+
2400+ // If search is empty, show all entries
2401+ if (searchTerm.trim() === '') {{
2402+ rows.forEach(row => {{
2403+ row.classList.remove('log-entry-hidden');
2404+ // Remove any existing highlights
2405+ const cells = row.querySelectorAll('td');
2406+ cells.forEach(cell => {{
2407+ cell.innerHTML = cell.innerHTML.replace(/<mark class="search-highlight">(.*?)<\\ /mark>/gi, '$1');
2408+ }});
2409+ visibleCount++;
2410+ }});
2411+ statusDiv.textContent = '';
2412+ return;
2413+ }}
2414+
2415+ // Filter and highlight entries
2416+ rows.forEach(row => {{
2417+ const rowText = row.textContent.toLowerCase();
2418+
2419+ if (rowText.includes(searchTerm)) {{
2420+ row.classList.remove('log-entry-hidden');
2421+ visibleCount++;
2422+
2423+ // Highlight matching text
2424+ const cells = row.querySelectorAll('td');
2425+ cells.forEach(cell => {{
2426+ // First remove any existing highlights
2427+ cell.innerHTML = cell.innerHTML.replace(/<mark class="search-highlight">(.*?)<\\ /mark>/gi, '$1');
2428+
2429+ // Then add new highlights
2430+ const regex = new RegExp(`(${{escapeRegExp(searchTerm)}})`, 'gi');
2431+ cell.innerHTML = cell.innerHTML.replace(regex, '<mark class="search-highlight">$1</mark>');
2432+ }});
2433+ }} else {{
2434+ row.classList.add('log-entry-hidden');
2435+ // Remove highlights from hidden rows
2436+ const cells = row.querySelectorAll('td');
2437+ cells.forEach(cell => {{
2438+ cell.innerHTML = cell.innerHTML.replace(/<mark class="search-highlight">(.*?)<\\ /mark>/gi, '$1');
2439+ }});
2440+ }}
2441+ }});
2442+
2443+ // Update status
2444+ statusDiv.textContent = `Showing ${{visibleCount}} of ${{totalCount}} entries`;
2445+ }}
2446+
2447+ // Clear search function
2448+ function clearLogSearch() {{
2449+ document.getElementById('logSearchInput').value = '';
2450+ filterLogEntries();
2451+ }}
2452+
2453+ // Escape special regex characters
2454+ function escapeRegExp(string) {{
2455+ return string.replace(/[.*+?^${{}}()|[\\ ]\\ \\ ]/g, '\\ \\ $&');
2456+ }}
2457+
22862458 // Fetch new log entries
22872459 async function updateLog() {{
22882460 if (isUpdating || isPaused) return;
0 commit comments