330 lines
9.7 KiB
HTML
330 lines
9.7 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>Browser Console Logs Viewer</title>
|
||
|
|
<style>
|
||
|
|
* {
|
||
|
|
margin: 0;
|
||
|
|
padding: 0;
|
||
|
|
box-sizing: border-box;
|
||
|
|
}
|
||
|
|
|
||
|
|
body {
|
||
|
|
font-family: 'Monaco', 'Courier New', monospace;
|
||
|
|
background: #1e1e1e;
|
||
|
|
color: #d4d4d4;
|
||
|
|
padding: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.container {
|
||
|
|
max-width: 1200px;
|
||
|
|
margin: 0 auto;
|
||
|
|
}
|
||
|
|
|
||
|
|
header {
|
||
|
|
margin-bottom: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
h1 {
|
||
|
|
font-size: 24px;
|
||
|
|
margin-bottom: 10px;
|
||
|
|
color: #4ec9b0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.controls {
|
||
|
|
display: flex;
|
||
|
|
gap: 10px;
|
||
|
|
margin-bottom: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
button {
|
||
|
|
padding: 8px 16px;
|
||
|
|
background: #0e639c;
|
||
|
|
color: white;
|
||
|
|
border: none;
|
||
|
|
border-radius: 4px;
|
||
|
|
cursor: pointer;
|
||
|
|
font-size: 14px;
|
||
|
|
}
|
||
|
|
|
||
|
|
button:hover {
|
||
|
|
background: #1177bb;
|
||
|
|
}
|
||
|
|
|
||
|
|
.filter-buttons button {
|
||
|
|
padding: 6px 12px;
|
||
|
|
font-size: 12px;
|
||
|
|
background: #2d2d2d;
|
||
|
|
color: #d4d4d4;
|
||
|
|
border: 1px solid #555;
|
||
|
|
}
|
||
|
|
|
||
|
|
.filter-buttons button.active {
|
||
|
|
background: #0e639c;
|
||
|
|
color: white;
|
||
|
|
border-color: #0e639c;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stats {
|
||
|
|
background: #252526;
|
||
|
|
padding: 12px;
|
||
|
|
border-radius: 4px;
|
||
|
|
margin-bottom: 20px;
|
||
|
|
font-size: 13px;
|
||
|
|
border-left: 4px solid #4ec9b0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.stats-item {
|
||
|
|
display: inline-block;
|
||
|
|
margin-right: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
#logs-container {
|
||
|
|
background: #1e1e1e;
|
||
|
|
border: 1px solid #3e3e42;
|
||
|
|
border-radius: 4px;
|
||
|
|
overflow: auto;
|
||
|
|
max-height: 600px;
|
||
|
|
padding: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.log-entry {
|
||
|
|
padding: 10px 15px;
|
||
|
|
border-bottom: 1px solid #3e3e42;
|
||
|
|
font-size: 12px;
|
||
|
|
line-height: 1.4;
|
||
|
|
display: flex;
|
||
|
|
gap: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.log-entry:hover {
|
||
|
|
background: #2d2d30;
|
||
|
|
}
|
||
|
|
|
||
|
|
.log-timestamp {
|
||
|
|
color: #858585;
|
||
|
|
flex-shrink: 0;
|
||
|
|
min-width: 200px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.log-level {
|
||
|
|
flex-shrink: 0;
|
||
|
|
min-width: 60px;
|
||
|
|
font-weight: bold;
|
||
|
|
}
|
||
|
|
|
||
|
|
.log-level.LOG {
|
||
|
|
color: #6a9955;
|
||
|
|
}
|
||
|
|
|
||
|
|
.log-level.ERROR {
|
||
|
|
color: #f48771;
|
||
|
|
}
|
||
|
|
|
||
|
|
.log-level.WARN {
|
||
|
|
color: #dcdcaa;
|
||
|
|
}
|
||
|
|
|
||
|
|
.log-level.INFO {
|
||
|
|
color: #4ec9b0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.log-level.DEBUG {
|
||
|
|
color: #9cdcfe;
|
||
|
|
}
|
||
|
|
|
||
|
|
.log-message {
|
||
|
|
flex: 1;
|
||
|
|
word-break: break-word;
|
||
|
|
white-space: pre-wrap;
|
||
|
|
}
|
||
|
|
|
||
|
|
.empty-state {
|
||
|
|
text-align: center;
|
||
|
|
padding: 40px 20px;
|
||
|
|
color: #858585;
|
||
|
|
}
|
||
|
|
|
||
|
|
.footer {
|
||
|
|
margin-top: 20px;
|
||
|
|
text-align: center;
|
||
|
|
font-size: 12px;
|
||
|
|
color: #858585;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="container">
|
||
|
|
<header>
|
||
|
|
<h1>🔍 Browser Console Logs</h1>
|
||
|
|
<p>Real-time capture of browser console output</p>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
<div class="stats" id="stats">
|
||
|
|
<div class="stats-item">Total Logs: <strong id="total-count">0</strong></div>
|
||
|
|
<div class="stats-item">Errors: <strong id="error-count" style="color: #f48771;">0</strong></div>
|
||
|
|
<div class="stats-item">Warnings: <strong id="warn-count" style="color: #dcdcaa;">0</strong></div>
|
||
|
|
<div class="stats-item">Last Updated: <strong id="last-update">Never</strong></div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="controls">
|
||
|
|
<button onclick="reloadLogs()">🔄 Reload</button>
|
||
|
|
<button onclick="clearLogs()">Clear All</button>
|
||
|
|
<button onclick="toggleAutoRefresh()" id="auto-refresh-btn">Auto-Refresh: OFF</button>
|
||
|
|
<div class="filter-buttons" style="display: flex; gap: 5px;">
|
||
|
|
<button class="active" onclick="filterLogs('all')" data-filter="all">All</button>
|
||
|
|
<button onclick="filterLogs('ERROR')" data-filter="ERROR">Errors</button>
|
||
|
|
<button onclick="filterLogs('WARN')" data-filter="WARN">Warnings</button>
|
||
|
|
<button onclick="filterLogs('LOG')" data-filter="LOG">Logs</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div id="logs-container">
|
||
|
|
<div class="empty-state">
|
||
|
|
<p>Waiting for console output...</p>
|
||
|
|
<p style="font-size: 11px; margin-top: 10px;">To capture logs, visit another page on this site and use console.log(), console.error(), etc.</p>
|
||
|
|
<button onclick="testLog()" style="margin-top: 15px; padding: 6px 12px; font-size: 12px; background: #2d2d2d; color: #d4d4d4; border: 1px solid #555; border-radius: 4px; cursor: pointer;">
|
||
|
|
📝 Add Test Log
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="footer">
|
||
|
|
<p>This page captures all browser console output in real-time. Refresh the page to continue monitoring.</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
let autoRefresh = false;
|
||
|
|
let currentFilter = 'all';
|
||
|
|
let allLogs = [];
|
||
|
|
|
||
|
|
function loadLogsFromStorage() {
|
||
|
|
try {
|
||
|
|
const stored = localStorage.getItem('console_logs');
|
||
|
|
if (stored) {
|
||
|
|
allLogs = JSON.parse(stored);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
console.error('Failed to load logs from storage:', e);
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
function saveLogsToStorage() {
|
||
|
|
try {
|
||
|
|
localStorage.setItem('console_logs', JSON.stringify(allLogs));
|
||
|
|
} catch (e) {
|
||
|
|
console.error('Failed to save logs to storage:', e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function fetchLogs() {
|
||
|
|
// Load from localStorage (primary source when using console-logger.js)
|
||
|
|
const loaded = loadLogsFromStorage();
|
||
|
|
|
||
|
|
renderLogs();
|
||
|
|
updateStats();
|
||
|
|
updateTimestamp();
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderLogs() {
|
||
|
|
const container = document.getElementById('logs-container');
|
||
|
|
|
||
|
|
const filteredLogs = allLogs.filter(log => {
|
||
|
|
return currentFilter === 'all' || log.level === currentFilter;
|
||
|
|
});
|
||
|
|
|
||
|
|
if (filteredLogs.length === 0) {
|
||
|
|
container.innerHTML = '<div class="empty-state"><p>No logs match the current filter</p></div>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
container.innerHTML = filteredLogs.map(log => `
|
||
|
|
<div class="log-entry">
|
||
|
|
<span class="log-timestamp">${new Date(log.timestamp).toLocaleTimeString()}</span>
|
||
|
|
<span class="log-level ${log.level}">${log.level}</span>
|
||
|
|
<span class="log-message">${escapeHtml(log.message)}</span>
|
||
|
|
</div>
|
||
|
|
`).join('');
|
||
|
|
|
||
|
|
// Auto-scroll to bottom
|
||
|
|
container.scrollTop = container.scrollHeight;
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateStats() {
|
||
|
|
document.getElementById('total-count').textContent = allLogs.length;
|
||
|
|
document.getElementById('error-count').textContent = allLogs.filter(l => l.level === 'ERROR').length;
|
||
|
|
document.getElementById('warn-count').textContent = allLogs.filter(l => l.level === 'WARN').length;
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateTimestamp() {
|
||
|
|
const now = new Date();
|
||
|
|
document.getElementById('last-update').textContent = now.toLocaleTimeString();
|
||
|
|
}
|
||
|
|
|
||
|
|
function clearLogs() {
|
||
|
|
if (confirm('Clear all logs?')) {
|
||
|
|
allLogs = [];
|
||
|
|
renderLogs();
|
||
|
|
updateStats();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function toggleAutoRefresh() {
|
||
|
|
autoRefresh = !autoRefresh;
|
||
|
|
const btn = document.getElementById('auto-refresh-btn');
|
||
|
|
btn.textContent = `Auto-Refresh: ${autoRefresh ? 'ON' : 'OFF'}`;
|
||
|
|
btn.style.background = autoRefresh ? '#0e639c' : '#2d2d2d';
|
||
|
|
}
|
||
|
|
|
||
|
|
function reloadLogs() {
|
||
|
|
fetchLogs();
|
||
|
|
}
|
||
|
|
|
||
|
|
function filterLogs(level) {
|
||
|
|
currentFilter = level;
|
||
|
|
document.querySelectorAll('.filter-buttons button').forEach(btn => {
|
||
|
|
btn.classList.toggle('active', btn.dataset.filter === level);
|
||
|
|
});
|
||
|
|
renderLogs();
|
||
|
|
}
|
||
|
|
|
||
|
|
function escapeHtml(text) {
|
||
|
|
const div = document.createElement('div');
|
||
|
|
div.textContent = text;
|
||
|
|
return div.innerHTML;
|
||
|
|
}
|
||
|
|
|
||
|
|
function testLog() {
|
||
|
|
console.log('📝 Test log entry - ' + new Date().toLocaleTimeString());
|
||
|
|
setTimeout(() => {
|
||
|
|
fetchLogs();
|
||
|
|
}, 100);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initial load
|
||
|
|
fetchLogs();
|
||
|
|
|
||
|
|
// Auto-refresh every 1 second if enabled
|
||
|
|
setInterval(() => {
|
||
|
|
if (autoRefresh) {
|
||
|
|
fetchLogs();
|
||
|
|
}
|
||
|
|
}, 1000);
|
||
|
|
|
||
|
|
// Inject console-logger.js into all frames on this domain
|
||
|
|
// This allows logs from other pages to be captured when accessed via logs.html
|
||
|
|
if (window.self === window.top) { // Only in main frame, not iframes
|
||
|
|
const script = document.createElement('script');
|
||
|
|
script.src = '/public/console-logger.js';
|
||
|
|
script.async = true;
|
||
|
|
document.head.appendChild(script);
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|