fix time filter and loader
This commit is contained in:
153
index.html
153
index.html
@@ -676,6 +676,57 @@
|
|||||||
.fade-in {
|
.fade-in {
|
||||||
animation: fadeIn .2s ease forwards
|
animation: fadeIn .2s ease forwards
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-container {
|
||||||
|
position: relative;
|
||||||
|
/* Crucial: pins the loader to this div */
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The Overlay */
|
||||||
|
.loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
/* Modern shorthand for top, left, bottom, right: 0 */
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
/* Dim the background */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10;
|
||||||
|
/* Ensures it stays on top of the chart */
|
||||||
|
color: white;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
visibility: hidden;
|
||||||
|
/* Hidden by default */
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show state */
|
||||||
|
.loading-overlay.is-active {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Simple Spinner Animation */
|
||||||
|
.spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-top-color: #3498db;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -694,7 +745,7 @@
|
|||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<div id="logo">log<span>scope</span></div>
|
<div id="logo">log<span>viewer</span></div>
|
||||||
<div class="hdr-sep"></div>
|
<div class="hdr-sep"></div>
|
||||||
<div id="file-info">No file loaded — drag & drop or select a file</div>
|
<div id="file-info">No file loaded — drag & drop or select a file</div>
|
||||||
<button id="file-btn" onclick="document.getElementById('file-input').click()">
|
<button id="file-btn" onclick="document.getElementById('file-input').click()">
|
||||||
@@ -707,6 +758,7 @@
|
|||||||
<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">
|
||||||
<!-- Filter bar -->
|
<!-- Filter bar -->
|
||||||
<div id="filter-bar">
|
<div id="filter-bar">
|
||||||
<div id="search-wrap">
|
<div id="search-wrap">
|
||||||
@@ -792,6 +844,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="loading-overlay" id="loader">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>Loading Data...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// ---- State ----
|
// ---- State ----
|
||||||
let allLogs = [];
|
let allLogs = [];
|
||||||
@@ -804,6 +862,7 @@
|
|||||||
let expandedRows = new Set();
|
let expandedRows = new Set();
|
||||||
let chart = null;
|
let chart = null;
|
||||||
let chartBuckets = [];
|
let chartBuckets = [];
|
||||||
|
const msg_len_limit = 100;
|
||||||
|
|
||||||
// ---- Demo data generator ----
|
// ---- Demo data generator ----
|
||||||
function generateDemoLogs() {
|
function generateDemoLogs() {
|
||||||
@@ -864,7 +923,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
debugger
|
//debugger
|
||||||
while (i < lines.length) {
|
while (i < lines.length) {
|
||||||
const line = lines[i].trim();
|
const line = lines[i].trim();
|
||||||
let matched = false;
|
let matched = false;
|
||||||
@@ -899,7 +958,14 @@
|
|||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
i = j - 1;
|
i = j - 1;
|
||||||
logs.push({ time, level: levelStr, message: msg.trim(), props: null, exception: exception || null });
|
logs.push({
|
||||||
|
time,
|
||||||
|
level: levelStr,
|
||||||
|
message: msg.length > msg_len_limit ? msg.slice(0, msg_len_limit) + "..." : msg.trim(),
|
||||||
|
fullmessage: msg.length > msg_len_limit ? msg : null,
|
||||||
|
props: null,
|
||||||
|
exception: exception || null
|
||||||
|
});
|
||||||
matched = true;
|
matched = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -932,7 +998,12 @@
|
|||||||
const usedKeys = new Set([...timeKeys, ...levelKeys, ...msgKeys].filter(k => obj[k] !== undefined));
|
const usedKeys = new Set([...timeKeys, ...levelKeys, ...msgKeys].filter(k => obj[k] !== undefined));
|
||||||
const props = Object.fromEntries(Object.entries(obj).filter(([k]) => !usedKeys.has(k)));
|
const props = Object.fromEntries(Object.entries(obj).filter(([k]) => !usedKeys.has(k)));
|
||||||
const exception = obj.exception || obj.error || obj.stack || null;
|
const exception = obj.exception || obj.error || obj.stack || null;
|
||||||
logs.push({ time, level: ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'].includes(level) ? level : 'INFO', message: String(msgVal), props: Object.keys(props).length ? props : null, exception: exception ? String(exception) : null });
|
logs.push({
|
||||||
|
time,
|
||||||
|
level: ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'].includes(level) ? level : 'INFO',
|
||||||
|
message: String(msgVal), props: Object.keys(props).length ? props : null,
|
||||||
|
exception: exception ? String(exception) : null
|
||||||
|
});
|
||||||
} catch { }
|
} catch { }
|
||||||
}
|
}
|
||||||
return logs;
|
return logs;
|
||||||
@@ -1016,7 +1087,39 @@
|
|||||||
x: {
|
x: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
stacked: true,
|
stacked: true,
|
||||||
ticks: { display: false },
|
ticks: {
|
||||||
|
// Grouping: Limit the number of labels shown to prevent overlapping
|
||||||
|
maxTicksLimit: 15,
|
||||||
|
// Formatting
|
||||||
|
// callback: function (val, index) {
|
||||||
|
// // Assuming your data labels are Date objects or Date strings
|
||||||
|
// const date = new Date(this.getLabelForValue(val));
|
||||||
|
|
||||||
|
// // Returns "HH:mm:ss" in local time
|
||||||
|
// return date.toLocaleTimeString('sv-SE');
|
||||||
|
// }
|
||||||
|
callback: function (val, index) {
|
||||||
|
const labels = this.chart.data.labels;
|
||||||
|
const date = new Date(labels[index]);
|
||||||
|
|
||||||
|
// Check if the first and last labels are on the same day
|
||||||
|
const firstDate = new Date(labels[0]).toDateString();
|
||||||
|
const lastDate = new Date(labels[labels.length - 1]).toDateString();
|
||||||
|
const isSameDay = firstDate === lastDate;
|
||||||
|
|
||||||
|
// Format based on the result
|
||||||
|
if (isSameDay) {
|
||||||
|
// Returns: HH:mm:ss
|
||||||
|
return date.toLocaleTimeString('sv-SE');
|
||||||
|
} else {
|
||||||
|
// Returns: DD/MM HH:mm:ss
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const time = date.toLocaleTimeString('sv-SE');
|
||||||
|
return `${day}/${month} ${time}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
grid: { color: '#ffffff08', drawBorder: false },
|
grid: { color: '#ffffff08', drawBorder: false },
|
||||||
border: { color: '#ffffff10' }
|
border: { color: '#ffffff10' }
|
||||||
},
|
},
|
||||||
@@ -1032,6 +1135,7 @@
|
|||||||
|
|
||||||
canvas.addEventListener('contextmenu', e => {
|
canvas.addEventListener('contextmenu', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
showLoading()
|
||||||
clearTimeFilter();
|
clearTimeFilter();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1043,19 +1147,25 @@
|
|||||||
|
|
||||||
// ---- Filtering ----
|
// ---- Filtering ----
|
||||||
function applyTimeFilter(start, end) {
|
function applyTimeFilter(start, end) {
|
||||||
|
showLoading()
|
||||||
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 =
|
||||||
`${fmtShort(startD)} – ${fmtShort(endD)}`;
|
`${fmtShort(startD)} – ${fmtShort(endD)}`;
|
||||||
document.getElementById('time-badge').classList.add('visible');
|
document.getElementById('time-badge').classList.add('visible');
|
||||||
applyFilters();
|
applyFilters();
|
||||||
|
updateFilteredLevelCounts();
|
||||||
|
hideLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearTimeFilter() {
|
function clearTimeFilter() {
|
||||||
|
showLoading()
|
||||||
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();
|
||||||
applyFilters();
|
applyFilters();
|
||||||
|
updateLevelCounts();
|
||||||
|
hideLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyFilters() {
|
function applyFilters() {
|
||||||
@@ -1103,9 +1213,9 @@
|
|||||||
tr.className = 'fade-in';
|
tr.className = 'fade-in';
|
||||||
tr.dataset.idx = idx;
|
tr.dataset.idx = idx;
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td class="col-time">${fmtTime(log.time)}</td>
|
<td class="col-time">${fmtTimeLocal(log.time)}</td>
|
||||||
<td class="col-level"><span class="lv lv-${log.level}">${log.level}</span></td>
|
<td class="col-level"><span class="lv lv-${log.level}">${log.level}</span></td>
|
||||||
<td class="col-msg">${esc(log.message)}${log.exception || log.props ? ' <span style="color:var(--text3);font-size:10px">▸ details</span>' : ''}</td>
|
<td class="col-msg">${esc(log.message)}${log.exception || log.props || log.fullmessage ? ' <span style="color:var(--text3);font-size:10px">▸ details</span>' : ''}</td>
|
||||||
`;
|
`;
|
||||||
tr.addEventListener('click', () => toggleDetail(tr, log, idx));
|
tr.addEventListener('click', () => toggleDetail(tr, log, idx));
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
@@ -1129,6 +1239,9 @@
|
|||||||
const detailTr = document.createElement('tr');
|
const detailTr = document.createElement('tr');
|
||||||
detailTr.className = 'row-detail';
|
detailTr.className = 'row-detail';
|
||||||
let sections = '';
|
let sections = '';
|
||||||
|
if (log.fullmessage) {
|
||||||
|
sections += `<div class="detail-section"><div class="detail-label">Full Message</div><div class="detail-val">${esc(log.fullmessage)}</div></div>`;
|
||||||
|
}
|
||||||
if (log.exception) {
|
if (log.exception) {
|
||||||
sections += `<div class="detail-section"><div class="detail-label">Exception / Stack Trace</div><div class="detail-val" style="color:var(--error)">${esc(log.exception)}</div></div>`;
|
sections += `<div class="detail-section"><div class="detail-label">Exception / Stack Trace</div><div class="detail-val" style="color:var(--error)">${esc(log.exception)}</div></div>`;
|
||||||
}
|
}
|
||||||
@@ -1149,6 +1262,14 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateFilteredLevelCounts() {
|
||||||
|
const counts = { TRACE: 0, DEBUG: 0, INFO: 0, WARN: 0, ERROR: 0, FATAL: 0 };
|
||||||
|
filteredLogs.forEach(l => { if (counts[l.level] !== undefined) counts[l.level]++; });
|
||||||
|
Object.entries(counts).forEach(([lv, cnt]) => {
|
||||||
|
document.getElementById('cnt-' + lv).textContent = cnt.toLocaleString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Status bar ----
|
// ---- Status bar ----
|
||||||
function updateStatus() {
|
function updateStatus() {
|
||||||
const s = document.getElementById('status-showing');
|
const s = document.getElementById('status-showing');
|
||||||
@@ -1161,6 +1282,9 @@
|
|||||||
function fmtTime(d) {
|
function fmtTime(d) {
|
||||||
return d.toISOString().replace('T', ' ').replace(/\.\d+Z$/, '');
|
return d.toISOString().replace('T', ' ').replace(/\.\d+Z$/, '');
|
||||||
}
|
}
|
||||||
|
function fmtTimeLocal(d) {
|
||||||
|
return d.toLocaleString('sv-SE').replace('T', ' ').replace(/\.\d+Z$/, '');
|
||||||
|
}
|
||||||
function fmtShort(d) {
|
function fmtShort(d) {
|
||||||
return d.toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
return d.toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||||
}
|
}
|
||||||
@@ -1171,6 +1295,7 @@
|
|||||||
// ---- Load file ----
|
// ---- Load file ----
|
||||||
function loadFile(file) {
|
function loadFile(file) {
|
||||||
//debugger
|
//debugger
|
||||||
|
showLoading();
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = e => {
|
reader.onload = e => {
|
||||||
const text = e.target.result;
|
const text = e.target.result;
|
||||||
@@ -1248,6 +1373,17 @@
|
|||||||
buildChart(allLogs);
|
buildChart(allLogs);
|
||||||
renderTable();
|
renderTable();
|
||||||
updateStatus();
|
updateStatus();
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = document.getElementById('loader');
|
||||||
|
function showLoading() {
|
||||||
|
if (!loader.classList.contains('is-active')) {
|
||||||
|
loader.classList.add('is-active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hideLoading() {
|
||||||
|
loader.classList.remove('is-active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Events ----
|
// ---- Events ----
|
||||||
@@ -1274,6 +1410,7 @@
|
|||||||
|
|
||||||
document.getElementById('time-badge').addEventListener('click', () => clearTimeFilter());
|
document.getElementById('time-badge').addEventListener('click', () => clearTimeFilter());
|
||||||
document.getElementById('clear-filters').addEventListener('click', () => {
|
document.getElementById('clear-filters').addEventListener('click', () => {
|
||||||
|
//showLoading()
|
||||||
levelFilter = null;
|
levelFilter = null;
|
||||||
document.getElementById('search').value = '';
|
document.getElementById('search').value = '';
|
||||||
document.querySelectorAll('.level-pill').forEach(p => p.classList.remove('active'));
|
document.querySelectorAll('.level-pill').forEach(p => p.classList.remove('active'));
|
||||||
@@ -1314,7 +1451,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Load demo on start
|
// Load demo on start
|
||||||
loadDemo();
|
//loadDemo();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user