Files
quality_app/py_app/app/templates/log_explorer.html
Quality App System d45dc1dab1 docs: Add comprehensive settings page analysis and improvements
- Add detailed settings page analysis report (settings.md)
- Document identified security vulnerabilities and code quality issues
- Provide prioritized improvement recommendations
- Document permission and access control issues
- Add testing checklist for validation
- Track modifications to settings.py, routes.py, and settings.html templates
2026-01-23 22:54:11 +02:00

253 lines
10 KiB
HTML

{% extends "base.html" %}
{% block title %}Log Explorer{% endblock %}
{% block content %}
<div style="padding: 20px; max-width: 1400px; margin: 0 auto;">
<div style="display: flex; align-items: center; gap: 15px; margin-bottom: 30px;">
<h1 style="margin: 0; color: var(--text-primary, #333); font-size: 2em;">📋 Log Explorer</h1>
<span style="background: var(--accent-color, #4caf50); color: white; padding: 6px 12px; border-radius: 6px; font-size: 0.85em; font-weight: 600;">Admin</span>
</div>
<div style="display: grid; grid-template-columns: 350px 1fr; gap: 20px; margin-bottom: 20px;">
<!-- Log Files List -->
<div style="background: var(--card-bg, white); border: 1px solid var(--border-color, #ddd); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column;">
<div style="padding: 15px; background: var(--header-bg, #f5f5f5); border-bottom: 1px solid var(--border-color, #ddd); display: flex; align-items: center; gap: 8px;">
<span style="font-size: 1.2em;">📁</span>
<strong>Log Files</strong>
</div>
<div id="logs-list" style="flex: 1; overflow-y: auto; padding: 10px; min-height: 400px;">
<div style="text-align: center; padding: 20px; color: var(--text-secondary, #666);">
<div style="font-size: 2em; margin-bottom: 10px;"></div>
<p>Loading log files...</p>
</div>
</div>
<div style="padding: 10px; border-top: 1px solid var(--border-color, #ddd); text-align: center; font-size: 0.85em; color: var(--text-secondary, #666);">
<span id="log-count">0</span> files
</div>
</div>
<!-- Log Content -->
<div style="background: var(--card-bg, white); border: 1px solid var(--border-color, #ddd); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column;">
<div style="padding: 15px; background: var(--header-bg, #f5f5f5); border-bottom: 1px solid var(--border-color, #ddd); display: flex; align-items: center; justify-content: space-between;">
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 1.2em;">📄</span>
<strong id="selected-log-name">Select a log file to view</strong>
</div>
<button id="download-log-btn" onclick="downloadCurrentLog()" style="display: none; background: #2196f3; color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 0.9em;">
⬇️ Download
</button>
</div>
<div id="log-content" style="flex: 1; overflow-y: auto; padding: 15px; font-family: 'Courier New', monospace; font-size: 0.85em; line-height: 1.5; background: var(--code-bg, #f9f9f9); color: var(--code-text, #333); white-space: pre-wrap; word-wrap: break-word; min-height: 400px;">
<div style="text-align: center; padding: 40px 20px; color: var(--text-secondary, #666);">
<div style="font-size: 2em; margin-bottom: 10px;">📖</div>
<p>Select a log file from the list to view its contents</p>
</div>
</div>
<!-- Pagination -->
<div id="pagination-controls" style="padding: 15px; background: var(--header-bg, #f5f5f5); border-top: 1px solid var(--border-color, #ddd); display: none; text-align: center; gap: 10px; display: flex; align-items: center; justify-content: center;">
<button id="prev-page-btn" onclick="previousPage()" style="background: #2196f3; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-weight: 600;">
← Previous
</button>
<span id="page-info" style="font-weight: 600; color: var(--text-primary, #333);">Page 1 of 1</span>
<button id="next-page-btn" onclick="nextPage()" style="background: #2196f3; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-weight: 600;">
Next →
</button>
<span id="lines-info" style="margin-left: auto; font-size: 0.9em; color: var(--text-secondary, #666);">0 total lines</span>
</div>
</div>
</div>
</div>
<script>
let currentLogFile = null;
let currentPage = 1;
let totalPages = 1;
// Load log files list on page load
document.addEventListener('DOMContentLoaded', function() {
loadLogsList();
});
function loadLogsList() {
fetch('/api/logs/list')
.then(response => response.json())
.then(data => {
if (data.success) {
renderLogsList(data.logs);
} else {
document.getElementById('logs-list').innerHTML = '<div style="padding: 20px; color: #d32f2f; text-align: center;">Failed to load logs</div>';
}
})
.catch(error => {
console.error('Error loading logs list:', error);
document.getElementById('logs-list').innerHTML = '<div style="padding: 20px; color: #d32f2f; text-align: center;">Error: ' + error.message + '</div>';
});
}
function renderLogsList(logs) {
const logsList = document.getElementById('logs-list');
if (logs.length === 0) {
logsList.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--text-secondary, #666);">No log files found</div>';
document.getElementById('log-count').textContent = '0';
return;
}
let html = '';
logs.forEach(log => {
html += `
<div onclick="viewLog('${log.name}')" style="padding: 12px; border-bottom: 1px solid var(--border-color, #ddd); cursor: pointer; transition: all 0.2s; background: var(--item-bg, transparent);" class="log-item" onmouseover="this.style.background='var(--hover-bg, #f0f0f0)'" onmouseout="this.style.background='var(--item-bg, transparent)'">
<div style="display: flex; align-items: center; gap: 8px;">
<span>📄</span>
<div style="flex: 1; min-width: 0;">
<div style="font-weight: 600; color: var(--text-primary, #333); word-break: break-word;">${log.name}</div>
<div style="font-size: 0.8em; color: var(--text-secondary, #666); margin-top: 4px;">
${log.size_formatted}${log.modified}
</div>
</div>
</div>
</div>
`;
});
logsList.innerHTML = html;
document.getElementById('log-count').textContent = logs.length;
}
function viewLog(filename) {
currentLogFile = filename;
currentPage = 1;
loadLogContent(filename);
}
function loadLogContent(filename) {
const logContent = document.getElementById('log-content');
logContent.innerHTML = '<div style="text-align: center; padding: 40px 20px;"><div style="font-size: 2em; margin-bottom: 10px;">⏳</div><p>Loading...</p></div>';
fetch(`/api/logs/view/${encodeURIComponent(filename)}?page=${currentPage}`)
.then(response => response.json())
.then(data => {
if (data.success) {
renderLogContent(data);
} else {
logContent.innerHTML = `<div style="color: #d32f2f; padding: 20px;">Error: ${data.message}</div>`;
}
})
.catch(error => {
console.error('Error loading log content:', error);
logContent.innerHTML = `<div style="color: #d32f2f; padding: 20px;">Error loading log: ${error.message}</div>`;
});
}
function renderLogContent(data) {
const logContent = document.getElementById('log-content');
const lines = data.lines || [];
if (lines.length === 0) {
logContent.textContent = '(Empty file)';
} else {
logContent.textContent = lines.join('');
}
// Update pagination
totalPages = data.total_pages;
currentPage = data.current_page;
const paginationControls = document.getElementById('pagination-controls');
if (totalPages > 1) {
paginationControls.style.display = 'flex';
document.getElementById('page-info').textContent = `Page ${currentPage} of ${totalPages}`;
document.getElementById('lines-info').textContent = `${data.total_lines} total lines`;
document.getElementById('prev-page-btn').disabled = currentPage === 1;
document.getElementById('next-page-btn').disabled = currentPage === totalPages;
} else {
paginationControls.style.display = 'none';
}
// Update header
document.getElementById('selected-log-name').textContent = data.filename;
document.getElementById('download-log-btn').style.display = 'block';
}
function previousPage() {
if (currentPage > 1) {
currentPage--;
loadLogContent(currentLogFile);
}
}
function nextPage() {
if (currentPage < totalPages) {
currentPage++;
loadLogContent(currentLogFile);
}
}
function downloadCurrentLog() {
if (!currentLogFile) return;
const link = document.createElement('a');
link.href = `/logs/${currentLogFile}`;
link.download = currentLogFile;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
</script>
<style>
#log-content {
-webkit-user-select: text;
-moz-user-select: text;
user-select: text;
}
#logs-list {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-color, #ccc) var(--scrollbar-bg, #f5f5f5);
}
#logs-list::-webkit-scrollbar {
width: 8px;
}
#logs-list::-webkit-scrollbar-track {
background: var(--scrollbar-bg, #f5f5f5);
}
#logs-list::-webkit-scrollbar-thumb {
background: var(--scrollbar-color, #ccc);
border-radius: 4px;
}
#log-content {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-color, #ccc) var(--scrollbar-bg, #f5f5f5);
}
#log-content::-webkit-scrollbar {
width: 8px;
}
#log-content::-webkit-scrollbar-track {
background: var(--scrollbar-bg, #f5f5f5);
}
#log-content::-webkit-scrollbar-thumb {
background: var(--scrollbar-color, #ccc);
border-radius: 4px;
}
@media (max-width: 768px) {
div[style*="display: grid"][style*="grid-template-columns: 350px"] {
grid-template-columns: 1fr !important;
}
}
</style>
{% endblock %}