401 lines
11 KiB
HTML
401 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Debug Panel - Norda Biznes Hub{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.debug-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: var(--spacing-lg);
|
|
}
|
|
|
|
.debug-header h1 {
|
|
font-size: var(--font-size-2xl);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.status-indicator {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
background: var(--error);
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
.status-indicator.connected {
|
|
background: var(--success);
|
|
}
|
|
|
|
.debug-controls {
|
|
display: flex;
|
|
gap: var(--spacing-md);
|
|
align-items: center;
|
|
}
|
|
|
|
.debug-controls select,
|
|
.debug-controls button {
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
font-size: var(--font-size-sm);
|
|
background: var(--surface);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.debug-controls button:hover {
|
|
background: var(--background);
|
|
}
|
|
|
|
.btn-danger {
|
|
background: var(--error) !important;
|
|
color: white !important;
|
|
border-color: var(--error) !important;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.log-container {
|
|
background: #1e1e1e;
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--spacing-md);
|
|
height: calc(100vh - 300px);
|
|
min-height: 400px;
|
|
overflow-y: auto;
|
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.log-entry {
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
margin-bottom: 2px;
|
|
display: flex;
|
|
gap: var(--spacing-md);
|
|
animation: fadeIn 0.2s ease-out;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(-5px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.log-entry.DEBUG { color: #9cdcfe; }
|
|
.log-entry.INFO { color: #4ec9b0; }
|
|
.log-entry.WARNING { color: #dcdcaa; background: rgba(220, 220, 170, 0.1); }
|
|
.log-entry.ERROR { color: #f48771; background: rgba(244, 135, 113, 0.1); }
|
|
|
|
.log-timestamp {
|
|
color: #6a9955;
|
|
flex-shrink: 0;
|
|
width: 85px;
|
|
}
|
|
|
|
.log-level {
|
|
flex-shrink: 0;
|
|
width: 60px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.log-logger {
|
|
color: #569cd6;
|
|
flex-shrink: 0;
|
|
width: 150px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.log-message {
|
|
flex: 1;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.log-location {
|
|
color: #808080;
|
|
font-size: 11px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.stats-bar {
|
|
display: flex;
|
|
gap: var(--spacing-xl);
|
|
margin-bottom: var(--spacing-lg);
|
|
padding: var(--spacing-md);
|
|
background: var(--surface);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.stat-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.stat-item .count {
|
|
font-weight: 700;
|
|
font-size: var(--font-size-lg);
|
|
}
|
|
|
|
.stat-item.info .count { color: var(--primary); }
|
|
.stat-item.warning .count { color: var(--warning); }
|
|
.stat-item.error .count { color: var(--error); }
|
|
|
|
.empty-logs {
|
|
color: #6a9955;
|
|
text-align: center;
|
|
padding: var(--spacing-2xl);
|
|
}
|
|
|
|
.auto-scroll-label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
font-size: var(--font-size-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="debug-header">
|
|
<h1>
|
|
<span class="status-indicator" id="statusIndicator"></span>
|
|
Debug Panel
|
|
</h1>
|
|
<div class="debug-controls">
|
|
<label class="auto-scroll-label">
|
|
<input type="checkbox" id="autoScroll" checked>
|
|
Auto-scroll
|
|
</label>
|
|
<select id="levelFilter">
|
|
<option value="">Wszystkie poziomy</option>
|
|
<option value="DEBUG">DEBUG</option>
|
|
<option value="INFO">INFO</option>
|
|
<option value="WARNING">WARNING</option>
|
|
<option value="ERROR">ERROR</option>
|
|
</select>
|
|
<button onclick="testLogs()">Test Logs</button>
|
|
<button onclick="clearLogs()" class="btn-danger">Wyczyść</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-bar">
|
|
<div class="stat-item info">
|
|
<span class="count" id="infoCount">0</span>
|
|
<span>INFO</span>
|
|
</div>
|
|
<div class="stat-item warning">
|
|
<span class="count" id="warningCount">0</span>
|
|
<span>WARNING</span>
|
|
</div>
|
|
<div class="stat-item error">
|
|
<span class="count" id="errorCount">0</span>
|
|
<span>ERROR</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="count" id="totalCount">0</span>
|
|
<span>Total</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="log-container" id="logContainer">
|
|
<div class="empty-logs" id="emptyMessage">
|
|
Oczekiwanie na logi... Wykonaj jakąś akcję na stronie.
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
const logContainer = document.getElementById('logContainer');
|
|
const emptyMessage = document.getElementById('emptyMessage');
|
|
const statusIndicator = document.getElementById('statusIndicator');
|
|
const levelFilter = document.getElementById('levelFilter');
|
|
const autoScrollCheckbox = document.getElementById('autoScroll');
|
|
|
|
let stats = { DEBUG: 0, INFO: 0, WARNING: 0, ERROR: 0 };
|
|
let allLogs = [];
|
|
let eventSource = null;
|
|
|
|
// Format timestamp
|
|
function formatTime(isoString) {
|
|
const date = new Date(isoString);
|
|
return date.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
}
|
|
|
|
// Add log entry to UI
|
|
function addLogEntry(log) {
|
|
allLogs.push(log);
|
|
stats[log.level] = (stats[log.level] || 0) + 1;
|
|
updateStats();
|
|
|
|
// Check filter
|
|
const filter = levelFilter.value;
|
|
if (filter && log.level !== filter) return;
|
|
|
|
emptyMessage.style.display = 'none';
|
|
|
|
const entry = document.createElement('div');
|
|
entry.className = `log-entry ${log.level}`;
|
|
entry.innerHTML = `
|
|
<span class="log-timestamp">${formatTime(log.timestamp)}</span>
|
|
<span class="log-level">${log.level}</span>
|
|
<span class="log-logger">${log.logger}</span>
|
|
<span class="log-message">${escapeHtml(log.message)}</span>
|
|
<span class="log-location">${log.module}:${log.lineno}</span>
|
|
`;
|
|
|
|
logContainer.appendChild(entry);
|
|
|
|
if (autoScrollCheckbox.checked) {
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
}
|
|
}
|
|
|
|
// Escape HTML
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// Update stats
|
|
function updateStats() {
|
|
document.getElementById('infoCount').textContent = stats.INFO || 0;
|
|
document.getElementById('warningCount').textContent = stats.WARNING || 0;
|
|
document.getElementById('errorCount').textContent = stats.ERROR || 0;
|
|
document.getElementById('totalCount').textContent = allLogs.length;
|
|
}
|
|
|
|
// Poll for new logs (SSE doesn't work well with session auth)
|
|
let lastLogTimestamp = '';
|
|
let pollInterval = null;
|
|
|
|
function startPolling() {
|
|
statusIndicator.classList.add('connected');
|
|
|
|
pollInterval = setInterval(async () => {
|
|
try {
|
|
const url = lastLogTimestamp
|
|
? `/api/admin/logs?limit=50&since=${encodeURIComponent(lastLogTimestamp)}`
|
|
: '/api/admin/logs?limit=50';
|
|
|
|
const response = await fetch(url, { credentials: 'include' });
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.logs.length > 0) {
|
|
data.logs.forEach(log => {
|
|
addLogEntry(log);
|
|
lastLogTimestamp = log.timestamp;
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Polling error:', error);
|
|
statusIndicator.classList.remove('connected');
|
|
}
|
|
}, 1000); // Poll every second
|
|
}
|
|
|
|
function stopPolling() {
|
|
if (pollInterval) {
|
|
clearInterval(pollInterval);
|
|
pollInterval = null;
|
|
}
|
|
statusIndicator.classList.remove('connected');
|
|
}
|
|
|
|
// Load initial logs
|
|
async function loadInitialLogs() {
|
|
try {
|
|
const response = await fetch('/api/admin/logs?limit=200', {
|
|
credentials: 'include'
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.logs.length > 0) {
|
|
data.logs.forEach(log => addLogEntry(log));
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load logs:', error);
|
|
}
|
|
}
|
|
|
|
// Filter logs
|
|
levelFilter.addEventListener('change', function() {
|
|
// Clear container
|
|
logContainer.innerHTML = '';
|
|
|
|
const filter = this.value;
|
|
const filtered = filter ? allLogs.filter(l => l.level === filter) : allLogs;
|
|
|
|
if (filtered.length === 0) {
|
|
logContainer.innerHTML = '<div class="empty-logs">Brak logów dla wybranego filtru</div>';
|
|
} else {
|
|
filtered.forEach(log => {
|
|
const entry = document.createElement('div');
|
|
entry.className = `log-entry ${log.level}`;
|
|
entry.innerHTML = `
|
|
<span class="log-timestamp">${formatTime(log.timestamp)}</span>
|
|
<span class="log-level">${log.level}</span>
|
|
<span class="log-logger">${log.logger}</span>
|
|
<span class="log-message">${escapeHtml(log.message)}</span>
|
|
<span class="log-location">${log.module}:${log.lineno}</span>
|
|
`;
|
|
logContainer.appendChild(entry);
|
|
});
|
|
}
|
|
});
|
|
|
|
// Clear logs
|
|
async function clearLogs() {
|
|
if (!confirm('Wyczyścić wszystkie logi?')) return;
|
|
|
|
try {
|
|
const response = await fetch('/api/admin/logs/clear', {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: {
|
|
'X-CSRFToken': '{{ csrf_token() }}'
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
allLogs = [];
|
|
stats = { DEBUG: 0, INFO: 0, WARNING: 0, ERROR: 0 };
|
|
updateStats();
|
|
logContainer.innerHTML = '<div class="empty-logs" id="emptyMessage">Logi wyczyszczone</div>';
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to clear logs:', error);
|
|
}
|
|
}
|
|
|
|
// Test logs
|
|
async function testLogs() {
|
|
try {
|
|
await fetch('/api/admin/test-log', {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: {
|
|
'X-CSRFToken': '{{ csrf_token() }}'
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to generate test logs:', error);
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
loadInitialLogs();
|
|
startPolling();
|
|
|
|
// Cleanup on page unload
|
|
window.addEventListener('beforeunload', stopPolling);
|
|
{% endblock %}
|