Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions static/css/logs.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,30 @@
}
.btn-clear:hover { opacity: 0.95; transform: translateY(-1px); }

/* Search bar */
.search-bar {
text-align: center;
margin: 12px 0;
}
.search-bar input {
width: 100%;
max-width: 400px;
padding: 8px 12px;
border: 1px solid rgba(255,255,255,0.15);
border-radius: 4px;
background: rgba(0,0,0,0.3);
color: #fff;
font-size: 0.95rem;
}
.search-bar input:focus {
outline: none;
border-color: #0af;
background: rgba(0,0,0,0.4);
}
.search-bar input::placeholder {
color: rgba(255,255,255,0.5);
}

/* Filter bar */
.filter-bar { text-align:center; margin: 8px 0 12px; }
.filter-btn {
Expand Down Expand Up @@ -86,19 +110,79 @@
font-weight: 700;
}

/* Pagination controls */
.pagination-controls {
text-align: center;
margin-top: 16px;
padding: 12px;
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.page-btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
background: rgba(0,0,0,0.45);
color: #fff;
font-weight: 700;
cursor: pointer;
transition: all 0.2s;
}
.page-btn:hover:not(:disabled) {
background: #0af;
color: #002;
}
.page-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.page-info {
padding: 8px 12px;
font-weight: 700;
color: inherit;
}
#pageSizeSelect {
padding: 8px 12px;
border: 1px solid rgba(255,255,255,0.15);
border-radius: 4px;
background: rgba(0,0,0,0.3);
color: #fff;
font-size: 0.9rem;
cursor: pointer;
}
#pageSizeSelect:focus {
outline: none;
border-color: #0af;
}

/* Theme-aware overrides: dark and light inherit base styles, but adjust backgrounds */
body.dark .container-logs .box { background: #222; border-color: rgba(255,255,255,0.03); }
body.dark .logs-table thead th { background: #333; }
body.dark .logs-table tbody tr:nth-child(even) { background: #222; color: #fff; }
body.dark .search-bar input { background: rgba(0,0,0,0.5); color: #fff; border-color: rgba(255,255,255,0.2); }
body.dark #pageSizeSelect { background: rgba(0,0,0,0.5); color: #fff; border-color: rgba(255,255,255,0.2); }
body.dark .page-info { color: #fff; }

body.light .container-logs .box { background: #f3f3f3; border-color: rgba(0,0,0,0.06); color: #000; }
body.light .logs-table thead th { background: #ddd; color: #000; }
body.light .logs-table tbody tr:nth-child(even) { background: #fff; color: #000; }
body.light .search-bar input { background: #fff; color: #000; border-color: #ccc; }
body.light .search-bar input::placeholder { color: rgba(0,0,0,0.5); }
body.light #pageSizeSelect { background: #fff; color: #000; border-color: #ccc; }
body.light .page-info { color: #000; }
body.retro-magazine .logs-table thead th { background: #000; color: #fff; }

/* Small screens */
@media (max-width: 640px) {
.container-logs { padding: 8px; margin: 40px 12px; }
.filter-btn { padding: 8px 10px; margin: 4px; }
.logs-table thead th, .logs-table tbody td { padding: 8px 6px; font-size: 0.92rem; }
.search-bar input { max-width: 100%; font-size: 0.9rem; }
.pagination-controls { gap: 8px; padding: 8px; }
.page-btn { padding: 6px 12px; font-size: 0.9rem; }
.page-info { font-size: 0.9rem; }
#pageSizeSelect { font-size: 0.85rem; }
}
119 changes: 112 additions & 7 deletions templates/logs.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ <h2>Activity Log ({{ log_size }} bytes)</h2>
<button type="submit" class="btn-clear">Clear Logs</button>
</form>

<!-- Search bar -->
<div class="search-bar">
<input type="text" id="searchInput" placeholder="Search logs..." aria-label="Search logs" onkeyup="searchLogs()">
</div>

<!-- Filter bar -->
<div class="filter-bar">
<button type="button" class="filter-btn" onclick="filterLogs('all')">All</button>
Expand All @@ -38,30 +43,130 @@ <h2>Activity Log ({{ log_size }} bytes)</h2>
</tbody>
</table>
</div>

<!-- Pagination controls -->
<div class="pagination-controls">
<button type="button" class="page-btn" id="prevBtn" onclick="changePage(-1)">Previous</button>
<span id="pageInfo" class="page-info">Page 1</span>
<button type="button" class="page-btn" id="nextBtn" onclick="changePage(1)">Next</button>
<select id="pageSizeSelect" onchange="changePageSize()">
<option value="10">10 per page</option>
<option value="25" selected>25 per page</option>
<option value="50">50 per page</option>
<option value="100">100 per page</option>
</select>
</div>
</div>
</div>
{% endblock %}

{% block scripts_extra %}
<script>
// Pagination and filtering state
let currentPage = 1;
let pageSize = 25;
let currentFilter = 'all';
let searchQuery = '';

document.addEventListener("DOMContentLoaded", () => {
// Apply saved theme (base.html provides setTheme/updateClock)
const saved = localStorage.getItem("theme") || "dark";
if (typeof setTheme === 'function') setTheme(saved);
if (typeof updateClock === 'function') updateClock();
setInterval(() => { if (typeof updateClock === 'function') updateClock(); }, 1000);

// Initialize pagination
applyPaginationAndFilters();
});

// Filter function (keeps original behavior)
// Search function
function searchLogs() {
searchQuery = document.getElementById('searchInput').value.toLowerCase();
currentPage = 1; // Reset to first page on search
applyPaginationAndFilters();
}

// Filter function (updated to work with pagination)
function filterLogs(type) {
const rows = document.querySelectorAll("#logsTable tbody tr");
rows.forEach(r => {
if (type === "all") {
r.style.display = "";
} else {
r.style.display = r.classList.contains(type) ? "" : "none";
currentFilter = type;
currentPage = 1; // Reset to first page on filter change
applyPaginationAndFilters();
}

// Change page
function changePage(delta) {
const allRows = Array.from(document.querySelectorAll("#logsTable tbody tr"));
const filteredRows = getFilteredRows(allRows);
const totalPages = Math.ceil(filteredRows.length / pageSize);

currentPage += delta;
if (currentPage < 1) currentPage = 1;
if (currentPage > totalPages) currentPage = totalPages;

applyPaginationAndFilters();
}

// Change page size
function changePageSize() {
pageSize = parseInt(document.getElementById('pageSizeSelect').value);
currentPage = 1; // Reset to first page
applyPaginationAndFilters();
}

// Get filtered rows based on current filters and search
function getFilteredRows(rows) {
return rows.filter(row => {
// Apply type filter
if (currentFilter !== 'all' && !row.classList.contains(currentFilter)) {
return false;
}

// Apply search filter
if (searchQuery) {
const text = row.textContent.toLowerCase();
if (!text.includes(searchQuery)) {
return false;
}
}

return true;
});
}

// Apply both pagination and filters
function applyPaginationAndFilters() {
const allRows = Array.from(document.querySelectorAll("#logsTable tbody tr"));
const filteredRows = getFilteredRows(allRows);

// Guard against division by zero
if (pageSize <= 0) pageSize = 25;

const totalPages = Math.ceil(filteredRows.length / pageSize) || 1;

// Ensure currentPage is within bounds
if (currentPage > totalPages) currentPage = totalPages;
if (currentPage < 1) currentPage = 1;

const startIdx = (currentPage - 1) * pageSize;
const endIdx = startIdx + pageSize;

// Hide all rows first
allRows.forEach(row => {
row.style.display = 'none';
});

// Show only the rows for current page
filteredRows.forEach((row, idx) => {
if (idx >= startIdx && idx < endIdx) {
row.style.display = '';
}
});

// Update pagination controls
document.getElementById('pageInfo').textContent =
`Page ${currentPage} of ${totalPages} (${filteredRows.length} entries)`;
document.getElementById('prevBtn').disabled = currentPage === 1 || filteredRows.length === 0;
document.getElementById('nextBtn').disabled = currentPage === totalPages || filteredRows.length === 0;
}
</script>
{% endblock %}