Initial commit — Server_Monitorizare_v2
This commit is contained in:
336
templates/logs.html
Normal file
336
templates/logs.html
Normal file
@@ -0,0 +1,336 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Logs - Server Monitoring{% endblock %}
|
||||
|
||||
{% block page_title %}Log Viewer{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.filter-container {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
border-left: 4px solid #dee2e6;
|
||||
margin-bottom: 10px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.log-entry.severity-error {
|
||||
border-left-color: #dc3545;
|
||||
}
|
||||
|
||||
.log-entry.severity-warning {
|
||||
border-left-color: #ffc107;
|
||||
}
|
||||
|
||||
.log-entry.severity-info {
|
||||
border-left-color: #17a2b8;
|
||||
}
|
||||
|
||||
.log-entry.severity-debug {
|
||||
border-left-color: #6c757d;
|
||||
}
|
||||
|
||||
.log-entry:hover {
|
||||
background-color: #f8f9fa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.log-meta {
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.severity-badge {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.3rem 0.6rem;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9rem;
|
||||
margin: 10px 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Filter Section -->
|
||||
<div class="filter-container">
|
||||
<form method="GET" class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label for="device_id" class="form-label">Device</label>
|
||||
<select class="form-select" name="device_id" id="device_id">
|
||||
<option value="">All Devices</option>
|
||||
{% for device in devices %}
|
||||
<option value="{{ device.id }}" {% if current_device_id == device.id %}selected{% endif %}>
|
||||
{{ device.hostname }} ({{ device.device_ip }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label for="severity" class="form-label">Severity</label>
|
||||
<select class="form-select" name="severity" id="severity">
|
||||
<option value="">All Levels</option>
|
||||
<option value="error" {% if current_severity == 'error' %}selected{% endif %}>Error</option>
|
||||
<option value="warning" {% if current_severity == 'warning' %}selected{% endif %}>Warning</option>
|
||||
<option value="info" {% if current_severity == 'info' %}selected{% endif %}>Info</option>
|
||||
<option value="debug" {% if current_severity == 'debug' %}selected{% endif %}>Debug</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="search" class="form-label">Search Message</label>
|
||||
<input type="text" class="form-control" name="search" id="search"
|
||||
placeholder="Search in log messages..." value="{{ current_search }}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<label for="per_page" class="form-label">Per Page</label>
|
||||
<select class="form-select" name="per_page" id="per_page">
|
||||
<option value="25" {% if pagination.per_page == 25 %}selected{% endif %}>25</option>
|
||||
<option value="50" {% if pagination.per_page == 50 %}selected{% endif %}>50</option>
|
||||
<option value="100" {% if pagination.per_page == 100 %}selected{% endif %}>100</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-1">
|
||||
<label class="form-label"> </label>
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Results Summary -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<strong>Showing {{ logs|length }} of {{ pagination.total|default(0) }} log entries</strong>
|
||||
{% if current_device_id or current_severity or current_search %}
|
||||
<a href="{{ url_for('main.logs') }}" class="btn btn-sm btn-outline-secondary ms-2">
|
||||
<i class="fas fa-times"></i> Clear Filters
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="btn btn-success btn-sm" onclick="refreshLogs()">
|
||||
<i class="fas fa-sync-alt"></i> Refresh
|
||||
</button>
|
||||
<button class="btn btn-info btn-sm" onclick="exportLogs()">
|
||||
<i class="fas fa-download"></i> Export
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Entries -->
|
||||
<div class="log-entries">
|
||||
{% if logs %}
|
||||
{% for log in logs %}
|
||||
<div class="card log-entry severity-{{ log.severity|default('info') }}"
|
||||
onclick="toggleLogDetail('{{ loop.index }}')">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="severity-badge bg-{% if log.severity == 'error' %}danger{% elif log.severity == 'warning' %}warning{% elif log.severity == 'info' %}primary{% else %}secondary{% endif %} text-white">
|
||||
{{ log.severity|default('info')|upper }}
|
||||
</span>
|
||||
|
||||
<strong class="ms-2">{{ log.device.hostname }}</strong>
|
||||
<span class="text-muted ms-2">({{ log.device.device_ip }})</span>
|
||||
|
||||
{% if log.device.nume_masa %}
|
||||
<span class="badge bg-info ms-2">{{ log.device.nume_masa }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="log-message">
|
||||
{{ log.resolved_message|default(log.full_message)|truncate(200) }}
|
||||
</div>
|
||||
|
||||
<div class="log-meta mt-2">
|
||||
<i class="fas fa-clock"></i> {{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') if log.timestamp else 'N/A' }}
|
||||
{% if log.template_hash %}
|
||||
<span class="ms-3"><i class="fas fa-tag"></i> Template: {{ log.template_hash[:8] }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end">
|
||||
<button class="btn btn-sm btn-outline-primary"
|
||||
onclick="event.stopPropagation(); viewDevice('{{ log.device_id }}')">
|
||||
<i class="fas fa-desktop"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detailed log view (initially hidden) -->
|
||||
<div id="detail-{{ loop.index }}" class="log-detail mt-3" style="display: none;">
|
||||
<hr>
|
||||
<h6>Full Message:</h6>
|
||||
<pre class="bg-light p-3 rounded">{{ log.full_message|default('No detailed message available') }}</pre>
|
||||
|
||||
{% if log.resolved_message != log.full_message %}
|
||||
<h6>Resolved Message:</h6>
|
||||
<div class="bg-info bg-opacity-10 p-3 rounded">
|
||||
{{ log.resolved_message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
|
||||
<h4 class="text-muted">No Logs Found</h4>
|
||||
<p class="text-muted">No log entries match the current filters.</p>
|
||||
{% if current_device_id or current_severity or current_search %}
|
||||
<a href="{{ url_for('main.logs') }}" class="btn btn-primary">
|
||||
<i class="fas fa-arrow-left"></i> View All Logs
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if pagination and pagination.total_pages > 1 %}
|
||||
<div class="pagination-container">
|
||||
<nav aria-label="Log pagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if pagination.has_prev %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ url_for('main.logs', page=pagination.prev_num, device_id=current_device_id, severity=current_severity, search=current_search, per_page=pagination.per_page) }}">
|
||||
<i class="fas fa-chevron-left"></i> Previous
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for page_num in range(1, pagination.total_pages + 1) %}
|
||||
{% if page_num == pagination.page %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ page_num }}</span>
|
||||
</li>
|
||||
{% elif page_num <= pagination.page + 2 and page_num >= pagination.page - 2 %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ url_for('main.logs', page=page_num, device_id=current_device_id, severity=current_severity, search=current_search, per_page=pagination.per_page) }}">
|
||||
{{ page_num }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if pagination.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ url_for('main.logs', page=pagination.next_num, device_id=current_device_id, severity=current_severity, search=current_search, per_page=pagination.per_page) }}">
|
||||
Next <i class="fas fa-chevron-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="text-center text-muted">
|
||||
Page {{ pagination.page }} of {{ pagination.total_pages }}
|
||||
({{ pagination.total }} total entries)
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
function toggleLogDetail(index) {
|
||||
const detail = document.getElementById(`detail-${index}`);
|
||||
if (detail.style.display === 'none') {
|
||||
detail.style.display = 'block';
|
||||
} else {
|
||||
detail.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function viewDevice(deviceId) {
|
||||
window.location.href = `{{ url_for('main.device_detail', device_id=0) }}`.replace('0', deviceId);
|
||||
}
|
||||
|
||||
function refreshLogs() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function exportLogs() {
|
||||
// Create export parameters from current filters
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.set('export', 'csv');
|
||||
|
||||
// Create download link
|
||||
const downloadUrl = `{{ url_for('main.logs') }}?${params.toString()}`;
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl;
|
||||
link.download = 'logs-export.csv';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
// Auto-refresh option
|
||||
let autoRefreshInterval;
|
||||
|
||||
function startAutoRefresh(seconds = 30) {
|
||||
autoRefreshInterval = setInterval(() => {
|
||||
refreshLogs();
|
||||
}, seconds * 1000);
|
||||
}
|
||||
|
||||
function stopAutoRefresh() {
|
||||
if (autoRefreshInterval) {
|
||||
clearInterval(autoRefreshInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize page
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Logs page loaded');
|
||||
|
||||
// Add keyboard shortcuts
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
if (event.key === 'f') {
|
||||
event.preventDefault();
|
||||
document.getElementById('search').focus();
|
||||
}
|
||||
if (event.key === 'r') {
|
||||
event.preventDefault();
|
||||
refreshLogs();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-submit form when filters change
|
||||
const filterForm = document.querySelector('.filter-container form');
|
||||
const autoSubmitElements = ['device_id', 'severity', 'per_page'];
|
||||
|
||||
autoSubmitElements.forEach(id => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.addEventListener('change', () => filterForm.submit());
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user