paging
This commit is contained in:
207
index.html
207
index.html
@@ -758,98 +758,93 @@
|
|||||||
<input type="file" id="file-input" accept=".log,.txt,.json,text/plain">
|
<input type="file" id="file-input" accept=".log,.txt,.json,text/plain">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="log-container">
|
<div id="filter-bar">
|
||||||
<!-- Filter bar -->
|
<div id="search-wrap">
|
||||||
<div id="filter-bar">
|
<svg width="13" height="13" viewBox="0 0 16 16" fill="none">
|
||||||
<div id="search-wrap">
|
<circle cx="6.5" cy="6.5" r="4.5" stroke="currentColor" stroke-width="1.4" />
|
||||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none">
|
<path d="M10.5 10.5L14 14" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
|
||||||
<circle cx="6.5" cy="6.5" r="4.5" stroke="currentColor" stroke-width="1.4" />
|
</svg>
|
||||||
<path d="M10.5 10.5L14 14" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
|
<input type="text" id="search" placeholder="Filter messages… (regex supported)" autocomplete="off">
|
||||||
|
<span id="regex-badge">.*</span>
|
||||||
|
</div>
|
||||||
|
<div class="hdr-sep"></div>
|
||||||
|
<div class="level-pills" id="level-pills">
|
||||||
|
<button class="level-pill" data-level="TRACE"><span class="pill-dot"></span>Trace<span class="pill-count"
|
||||||
|
id="cnt-TRACE">0</span></button>
|
||||||
|
<button class="level-pill" data-level="DEBUG"><span class="pill-dot"></span>Debug<span class="pill-count"
|
||||||
|
id="cnt-DEBUG">0</span></button>
|
||||||
|
<button class="level-pill" data-level="INFO"><span class="pill-dot"></span>Info<span class="pill-count"
|
||||||
|
id="cnt-INFO">0</span></button>
|
||||||
|
<button class="level-pill" data-level="WARN"><span class="pill-dot"></span>Warn<span class="pill-count"
|
||||||
|
id="cnt-WARN">0</span></button>
|
||||||
|
<button class="level-pill" data-level="ERROR"><span class="pill-dot"></span>Error<span class="pill-count"
|
||||||
|
id="cnt-ERROR">0</span></button>
|
||||||
|
<button class="level-pill" data-level="FATAL"><span class="pill-dot"></span>Fatal<span class="pill-count"
|
||||||
|
id="cnt-FATAL">0</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="filter-actions">
|
||||||
|
<div id="time-badge">
|
||||||
|
<svg width="11" height="11" viewBox="0 0 16 16" fill="none">
|
||||||
|
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.4" />
|
||||||
|
<path d="M8 5v3l2 2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
<input type="text" id="search" placeholder="Filter messages… (regex supported)" autocomplete="off">
|
<span id="time-badge-label">time range</span>
|
||||||
<span id="regex-badge">.*</span>
|
<span class="tx-close">✕</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hdr-sep"></div>
|
<button id="clear-filters">Clear filters</button>
|
||||||
<div class="level-pills" id="level-pills">
|
</div>
|
||||||
<button class="level-pill" data-level="TRACE"><span class="pill-dot"></span>Trace<span class="pill-count"
|
</div>
|
||||||
id="cnt-TRACE">0</span></button>
|
|
||||||
<button class="level-pill" data-level="DEBUG"><span class="pill-dot"></span>Debug<span class="pill-count"
|
<!-- Main -->
|
||||||
id="cnt-DEBUG">0</span></button>
|
<div id="main">
|
||||||
<button class="level-pill" data-level="INFO"><span class="pill-dot"></span>Info<span class="pill-count"
|
<!-- Chart -->
|
||||||
id="cnt-INFO">0</span></button>
|
<div id="chart-panel">
|
||||||
<button class="level-pill" data-level="WARN"><span class="pill-dot"></span>Warn<span class="pill-count"
|
<div id="chart-wrap">
|
||||||
id="cnt-WARN">0</span></button>
|
<canvas id="intensity-chart"></canvas>
|
||||||
<button class="level-pill" data-level="ERROR"><span class="pill-dot"></span>Error<span class="pill-count"
|
<span id="chart-hint">drag to zoom · right-click to reset</span>
|
||||||
id="cnt-ERROR">0</span></button>
|
|
||||||
<button class="level-pill" data-level="FATAL"><span class="pill-dot"></span>Fatal<span class="pill-count"
|
|
||||||
id="cnt-FATAL">0</span></button>
|
|
||||||
</div>
|
|
||||||
<div class="filter-actions">
|
|
||||||
<div id="time-badge">
|
|
||||||
<svg width="11" height="11" viewBox="0 0 16 16" fill="none">
|
|
||||||
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.4" />
|
|
||||||
<path d="M8 5v3l2 2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
|
|
||||||
</svg>
|
|
||||||
<span id="time-badge-label">time range</span>
|
|
||||||
<span class="tx-close">✕</span>
|
|
||||||
</div>
|
|
||||||
<button id="clear-filters">Clear filters</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main -->
|
<!-- Empty state / Table area -->
|
||||||
<div id="main">
|
<div id="empty-state">
|
||||||
<!-- Chart -->
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||||
<div id="chart-panel">
|
<rect x="6" y="6" width="36" height="36" rx="6" stroke="currentColor" stroke-width="1.5" stroke-dasharray="5 3" />
|
||||||
<div id="chart-wrap">
|
<path d="M16 20h16M16 24h10M16 28h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||||
<canvas id="intensity-chart"></canvas>
|
</svg>
|
||||||
<span id="chart-hint">drag to zoom · right-click to reset</span>
|
<div class="es-title">No log file loaded</div>
|
||||||
</div>
|
<div class="es-sub">Drag & drop a log file anywhere on the page or click below to browse</div>
|
||||||
</div>
|
<button class="es-btn" onclick="document.getElementById('file-input').click()">Select log file</button>
|
||||||
|
</div>
|
||||||
<!-- Empty state / Table area -->
|
|
||||||
<div id="empty-state">
|
<div id="table-area" style="display:none">
|
||||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none">
|
<div id="dt-wrap">
|
||||||
<rect x="6" y="6" width="36" height="36" rx="6" stroke="currentColor" stroke-width="1.5"
|
<table id="log-table">
|
||||||
stroke-dasharray="5 3" />
|
<thead>
|
||||||
<path d="M16 20h16M16 24h10M16 28h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
<tr>
|
||||||
</svg>
|
<th class="col-time" data-col="time">Time</th>
|
||||||
<div class="es-title">No log file loaded</div>
|
<th class="col-level" data-col="level">Level</th>
|
||||||
<div class="es-sub">Drag & drop a log file anywhere on the page or click below to browse</div>
|
<th class="col-msg" data-col="message">Message</th>
|
||||||
<button class="es-btn" onclick="document.getElementById('file-input').click()">Select log file</button>
|
</tr>
|
||||||
</div>
|
</thead>
|
||||||
|
<tbody id="log-tbody"></tbody>
|
||||||
<div id="table-area" style="display:none">
|
</table>
|
||||||
<div id="dt-wrap">
|
|
||||||
<table id="log-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="col-time" data-col="time">Time</th>
|
|
||||||
<th class="col-level" data-col="level">Level</th>
|
|
||||||
<th class="col-msg" data-col="message">Message</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="log-tbody"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status bar -->
|
|
||||||
<div id="status-bar" style="display:none">
|
|
||||||
<span class="status-count" id="status-showing">0 entries</span>
|
|
||||||
<span class="sb-sep">·</span>
|
|
||||||
<span id="status-total">0 total</span>
|
|
||||||
<span class="sb-sep">·</span>
|
|
||||||
<span id="status-file">no file</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="loading-overlay" id="loader">
|
<!-- Status bar -->
|
||||||
<div class="spinner"></div>
|
<div id="status-bar" style="display:none">
|
||||||
<p>Loading Data...</p>
|
<span class="status-count" id="status-showing">0 entries</span>
|
||||||
|
<span class="sb-sep">·</span>
|
||||||
|
<span id="status-total">0 total</span>
|
||||||
|
<span class="sb-sep">·</span>
|
||||||
|
<span id="status-file">no file</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="loading-overlay" id="loader">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>Loading Data...</p>
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
// ---- State ----
|
// ---- State ----
|
||||||
let allLogs = [];
|
let allLogs = [];
|
||||||
@@ -863,6 +858,8 @@
|
|||||||
let chart = null;
|
let chart = null;
|
||||||
let chartBuckets = [];
|
let chartBuckets = [];
|
||||||
const msg_len_limit = 100;
|
const msg_len_limit = 100;
|
||||||
|
let currentPage = 1;
|
||||||
|
const PAGE_SIZE = 500;
|
||||||
|
|
||||||
// ---- Demo data generator ----
|
// ---- Demo data generator ----
|
||||||
function generateDemoLogs() {
|
function generateDemoLogs() {
|
||||||
@@ -1148,6 +1145,7 @@
|
|||||||
// ---- Filtering ----
|
// ---- Filtering ----
|
||||||
function applyTimeFilter(start, end) {
|
function applyTimeFilter(start, end) {
|
||||||
showLoading()
|
showLoading()
|
||||||
|
currentPage = 1;
|
||||||
timeFilter = { start, end };
|
timeFilter = { start, end };
|
||||||
const startD = new Date(start), endD = new Date(end);
|
const startD = new Date(start), endD = new Date(end);
|
||||||
document.getElementById('time-badge-label').textContent =
|
document.getElementById('time-badge-label').textContent =
|
||||||
@@ -1160,6 +1158,7 @@
|
|||||||
|
|
||||||
function clearTimeFilter() {
|
function clearTimeFilter() {
|
||||||
showLoading()
|
showLoading()
|
||||||
|
currentPage = 1;
|
||||||
timeFilter = null;
|
timeFilter = null;
|
||||||
document.getElementById('time-badge').classList.remove('visible');
|
document.getElementById('time-badge').classList.remove('visible');
|
||||||
if (chart) chart.resetZoom();
|
if (chart) chart.resetZoom();
|
||||||
@@ -1169,6 +1168,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyFilters() {
|
function applyFilters() {
|
||||||
|
currentPage = 1;
|
||||||
let regex = null;
|
let regex = null;
|
||||||
const searchVal = document.getElementById('search').value;
|
const searchVal = document.getElementById('search').value;
|
||||||
const searchEl = document.getElementById('search');
|
const searchEl = document.getElementById('search');
|
||||||
@@ -1204,11 +1204,15 @@
|
|||||||
return sortDir === 'asc' ? (av > bv ? 1 : -1) : (av < bv ? 1 : -1);
|
return sortDir === 'asc' ? (av > bv ? 1 : -1) : (av < bv ? 1 : -1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const totalPages = Math.max(1, Math.ceil(sorted.length / PAGE_SIZE));
|
||||||
|
currentPage = Math.min(currentPage, totalPages);
|
||||||
|
const pageData = sorted.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE);
|
||||||
|
|
||||||
expandedRows.clear();
|
expandedRows.clear();
|
||||||
const tbody = document.getElementById('log-tbody');
|
const tbody = document.getElementById('log-tbody');
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
sorted.forEach((log, idx) => {
|
pageData.forEach((log, idx) => {
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
tr.className = 'fade-in';
|
tr.className = 'fade-in';
|
||||||
tr.dataset.idx = idx;
|
tr.dataset.idx = idx;
|
||||||
@@ -1221,6 +1225,8 @@
|
|||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
renderPager(totalPages);
|
||||||
|
|
||||||
// Sort indicators
|
// Sort indicators
|
||||||
document.querySelectorAll('thead th').forEach(th => {
|
document.querySelectorAll('thead th').forEach(th => {
|
||||||
th.classList.remove('sort-asc', 'sort-desc');
|
th.classList.remove('sort-asc', 'sort-desc');
|
||||||
@@ -1228,6 +1234,37 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderPager(totalPages) {
|
||||||
|
let el = document.getElementById('pager');
|
||||||
|
if (!el) {
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.id = 'pager';
|
||||||
|
el.style.cssText = 'display:flex;align-items:center;gap:8px;padding:8px 20px;border-top:1px solid var(--border);background:var(--surface);font-size:11px;font-family:var(--font-mono);color:var(--text2);flex-shrink:0';
|
||||||
|
document.getElementById('status-bar').before(el);
|
||||||
|
}
|
||||||
|
const btn = (label, page, disabled) =>
|
||||||
|
`<button onclick="goPage(${page})" ${disabled ? 'disabled' : ''} style="padding:3px 9px;border-radius:var(--radius);border:1px solid var(--border2);background:${page === currentPage ? 'var(--accent-dim)' : 'transparent'};color:${page === currentPage ? 'var(--accent)' : 'var(--text2)'};cursor:${disabled ? 'default' : 'pointer'};font-family:var(--font-mono);font-size:11px">${label}</button>`;
|
||||||
|
|
||||||
|
const pages = [];
|
||||||
|
for (let p = 1; p <= totalPages; p++) {
|
||||||
|
if (p === 1 || p === totalPages || Math.abs(p - currentPage) <= 1) pages.push(p);
|
||||||
|
else if (pages[pages.length - 1] !== '…') pages.push('…');
|
||||||
|
}
|
||||||
|
|
||||||
|
el.innerHTML = btn('‹', currentPage - 1, currentPage === 1)
|
||||||
|
+ pages.map(p => p === '…' ? `<span style="color:var(--text3)">…</span>` : btn(p, p, false)).join('')
|
||||||
|
+ btn('›', currentPage + 1, currentPage === totalPages)
|
||||||
|
+ `<span style="margin-left:8px;color:var(--text3)">page ${currentPage} / ${totalPages}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.goPage = function (p) {
|
||||||
|
const totalPages = Math.ceil(filteredLogs.length / PAGE_SIZE);
|
||||||
|
if (p < 1 || p > totalPages) return;
|
||||||
|
currentPage = p;
|
||||||
|
renderTable();
|
||||||
|
document.getElementById('dt-wrap').scrollTop = 0;
|
||||||
|
};
|
||||||
|
|
||||||
function toggleDetail(tr, log, idx) {
|
function toggleDetail(tr, log, idx) {
|
||||||
if (expandedRows.has(idx)) {
|
if (expandedRows.has(idx)) {
|
||||||
expandedRows.delete(idx);
|
expandedRows.delete(idx);
|
||||||
@@ -1385,7 +1422,7 @@
|
|||||||
function hideLoading() {
|
function hideLoading() {
|
||||||
loader.classList.remove('is-active');
|
loader.classList.remove('is-active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Events ----
|
// ---- Events ----
|
||||||
document.getElementById('file-input').addEventListener('change', e => {
|
document.getElementById('file-input').addEventListener('change', e => {
|
||||||
if (e.target.files[0]) loadFile(e.target.files[0]);
|
if (e.target.files[0]) loadFile(e.target.files[0]);
|
||||||
|
|||||||
Reference in New Issue
Block a user