Files
pyMC_Repeater/repeater/templates/logs.html
Lloyd 97256eb132 Initial commit: PyMC Repeater Daemon
This commit sets up the initial project structure for the PyMC Repeater Daemon.
It includes base configuration files, dependency definitions, and scaffolding
for the main daemon service responsible for handling PyMC repeating operations.
2025-10-24 23:13:48 +01:00

239 lines
9.0 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>pyMC Repeater - Logs</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="layout">
<!-- Navigation Component -->
<!-- NAVIGATION_PLACEHOLDER -->
<!-- Main Content -->
<main class="content">
<header>
<h1>System Logs</h1>
<p>Real-time system events and diagnostics</p>
</header>
<!-- Filter Controls -->
<div style="margin-bottom: 20px; padding: 15px; background: var(--color-bg-secondary); border-radius: 8px; border: 1px solid var(--color-border);">
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 15px;">
<label style="font-weight: bold; margin: 0; width: 100%;">Logger Filters:</label>
<button id="selectAllBtn" style="flex: 1; min-width: 100px; padding: 8px 12px; background: var(--color-accent-primary); color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9em;">Select All</button>
<button id="clearAllBtn" style="flex: 1; min-width: 100px; padding: 8px 12px; background: var(--color-accent-secondary); color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9em;">Clear All</button>
</div>
<div id="filterContainer" style="display: flex; flex-wrap: wrap; gap: 8px;">
<!-- Filters will be added here dynamically -->
</div>
</div>
<!-- Mock log data - will be replaced with real logs via API -->
<div class="log-container" id="logs">
<div class="log-line">
<span class="log-time">[Loading...]</span>
<span class="log-level info">INFO</span>
<span class="log-msg">Fetching system logs...</span>
</div>
</div>
</main>
</div>
<script>
// Track filter state and all logs
let allLogs = [];
let enabledLoggers = new Set();
let allLoggers = new Set();
// Fetch logs from API on page load
async function loadLogs() {
try {
const response = await fetch('/api/logs');
const data = await response.json();
if (data.logs && data.logs.length > 0) {
allLogs = data.logs;
// Extract all unique logger names
const newLoggers = new Set();
allLogs.forEach(log => {
const loggerName = extractLoggerName(log.message);
newLoggers.add(loggerName);
});
// On first load, enable all detected loggers
if (enabledLoggers.size === 0) {
enabledLoggers = new Set(newLoggers);
}
// Check if logger set has changed
const loggersChanged = !setsEqual(allLoggers, newLoggers);
// Update allLoggers with currently active loggers only
allLoggers = newLoggers;
// Only update filter UI if the set of loggers changed
if (loggersChanged) {
updateFilterUI();
}
// Always display filtered logs (but don't rebuild filters)
displayLogs();
}
} catch (error) {
console.error('Error loading logs:', error);
const logsContainer = document.getElementById('logs');
logsContainer.innerHTML = `
<div class="log-line">
<span class="log-time">[Error]</span>
<span class="log-level error">ERROR</span>
<span class="log-msg">Failed to load logs: ${escapeHtml(error.message)}</span>
</div>
`;
}
}
// Helper to compare two sets
function setsEqual(set1, set2) {
if (set1.size !== set2.size) return false;
for (let item of set1) {
if (!set2.has(item)) return false;
}
return true;
}
// Extract logger name from log message (e.g., "RepeaterDaemon", "HTTPServer", etc.)
function extractLoggerName(message) {
// Format: "2025-10-22 12:47:30,270 - LoggerName - LEVEL - message"
const match = message.match(/- (\w+) -/);
return match ? match[1] : 'Unknown';
}
// Update filter UI with detected loggers
function updateFilterUI() {
const filterContainer = document.getElementById('filterContainer');
const sortedLoggers = Array.from(allLoggers).sort();
// Clear existing buttons
filterContainer.innerHTML = '';
sortedLoggers.forEach(logger => {
const button = document.createElement('button');
button.className = 'filter-btn';
button.dataset.logger = logger;
button.textContent = logger;
button.style.cssText = `
padding: 8px 14px;
border: 2px solid var(--color-border);
background: var(--color-bg-tertiary);
color: var(--color-text-primary);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
font-size: 0.9em;
`;
// Set active state based on enabledLoggers
if (enabledLoggers.has(logger)) {
button.style.background = 'var(--color-accent-primary)';
button.style.color = 'white';
button.style.borderColor = 'var(--color-accent-primary)';
}
button.addEventListener('click', () => {
if (enabledLoggers.has(logger)) {
enabledLoggers.delete(logger);
} else {
enabledLoggers.add(logger);
}
updateFilterUI();
displayLogs();
});
filterContainer.appendChild(button);
});
}
// Display logs filtered by enabled loggers
function displayLogs() {
const logsContainer = document.getElementById('logs');
logsContainer.innerHTML = '';
allLogs.forEach(log => {
const loggerName = extractLoggerName(log.message);
// Skip if logger is not enabled
if (!enabledLoggers.has(loggerName)) {
return;
}
const logLine = document.createElement('div');
logLine.className = 'log-line';
// Try to parse timestamp
const timestamp = new Date(log.timestamp);
const timeStr = timestamp.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// Get log level from API response or parse from message
let level = log.level || 'INFO';
let levelClass = level.toLowerCase();
logLine.innerHTML = `
<span class="log-time">[${timeStr}]</span>
<span class="log-level ${levelClass}">${level}</span>
<span class="log-msg">${escapeHtml(log.message || '')}</span>
`;
logsContainer.appendChild(logLine);
});
// Auto-scroll to bottom
logsContainer.scrollTop = logsContainer.scrollHeight;
}
// Helper function to escape HTML
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, m => map[m]);
}
// Setup button event listeners
function setupButtons() {
document.getElementById('selectAllBtn').addEventListener('click', () => {
enabledLoggers = new Set(allLoggers);
updateFilterUI();
displayLogs();
});
document.getElementById('clearAllBtn').addEventListener('click', () => {
enabledLoggers.clear();
updateFilterUI();
displayLogs();
});
}
// Load logs on page load
document.addEventListener('DOMContentLoaded', () => {
setupButtons();
loadLogs();
});
// Refresh logs every 5 seconds
setInterval(loadLogs, 5000);
</script>
</body>
</html>