mirror of
https://github.com/rightup/pyMC_Repeater.git
synced 2026-03-28 17:43:06 +01:00
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.
239 lines
9.0 KiB
HTML
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 = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
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>
|