334 lines
11 KiB
HTML
334 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Dashboard - Server Monitoring{% endblock %}
|
|
|
|
{% block page_title %}Dashboard{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.stats-row {
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.stat-card {
|
|
height: 120px;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 20px;
|
|
border-left: 4px solid #3498db;
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.stat-card:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.stat-card.devices {
|
|
border-left-color: #2ecc71;
|
|
}
|
|
|
|
.stat-card.logs {
|
|
border-left-color: #e74c3c;
|
|
}
|
|
|
|
.stat-card.templates {
|
|
border-left-color: #f39c12;
|
|
}
|
|
|
|
.stat-card.active {
|
|
border-left-color: #9b59b6;
|
|
}
|
|
|
|
.stat-icon {
|
|
font-size: 2.5rem;
|
|
margin-right: 20px;
|
|
}
|
|
|
|
.stat-details h3 {
|
|
margin: 0;
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.stat-details p {
|
|
margin: 0;
|
|
color: #7f8c8d;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.refresh-timer {
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
font-size: 1.1rem;
|
|
color: #2c3e50;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.action-buttons {
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.action-buttons .btn {
|
|
margin: 5px;
|
|
}
|
|
|
|
.recent-logs-section {
|
|
margin-top: 30px;
|
|
}
|
|
|
|
.activity-section {
|
|
margin-top: 30px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Auto-refresh Timer -->
|
|
<div class="refresh-timer">
|
|
<i class="fas fa-clock"></i>
|
|
Time until refresh: <span id="refresh-timer">30</span> seconds
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="action-buttons">
|
|
<a href="{{ url_for('main.devices') }}" class="btn btn-primary">
|
|
<i class="fas fa-desktop"></i> Manage Devices
|
|
</a>
|
|
<a href="{{ url_for('main.logs') }}" class="btn btn-secondary">
|
|
<i class="fas fa-list-alt"></i> View All Logs
|
|
</a>
|
|
<a href="{{ url_for('main.stats') }}" class="btn btn-info">
|
|
<i class="fas fa-chart-bar"></i> System Stats
|
|
</a>
|
|
<a href="{{ url_for('ansible_web.index') }}" class="btn btn-success">
|
|
<i class="fas fa-cogs"></i> Automation
|
|
</a>
|
|
<button class="btn btn-danger" onclick="resetDatabase(event)" title="Clear all logs and reset database">
|
|
<i class="fas fa-trash-alt"></i> Clear Database
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Activity in Last 24 Hours -->
|
|
{% if activity_stats %}
|
|
<div class="activity-section">
|
|
<h4><i class="fas fa-clock"></i> Last 24 Hours Activity</h4>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title">{{ activity_stats.logs_24h|default(0) }}</h5>
|
|
<p class="card-text text-muted">New Log Entries</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title">{{ activity_stats.devices_seen_24h|default(0) }}</h5>
|
|
<p class="card-text text-muted">Devices Active</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Recent Logs Section -->
|
|
<div class="recent-logs-section">
|
|
<h4><i class="fas fa-list-alt"></i> Recent Activity</h4>
|
|
<div class="table-container">
|
|
<table class="table table-striped table-hover">
|
|
<thead class="table-dark">
|
|
<tr>
|
|
<th width="20%">Device</th>
|
|
<th width="15%">IP Address</th>
|
|
<th width="15%">Location</th>
|
|
<th width="20%">Timestamp</th>
|
|
<th width="30%">Event Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% if recent_logs %}
|
|
{% for log in recent_logs %}
|
|
<tr>
|
|
<td>
|
|
<a href="{{ url_for('main.device_detail', device_id=log.device_id) }}" class="text-decoration-none">
|
|
<strong>{{ log.device.hostname }}</strong>
|
|
</a>
|
|
</td>
|
|
<td>{{ log.device.device_ip }}</td>
|
|
<td>
|
|
{% if log.device.nume_masa %}
|
|
<span class="badge bg-info">{{ log.device.nume_masa }}</span>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<small>{{ log.timestamp | local_dt('%Y-%m-%d %H:%M:%S') if log.timestamp else 'N/A' }}</small>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if log.severity == 'error' %}danger{% elif log.severity == 'warning' %}warning{% elif log.severity == 'info' %}primary{% else %}secondary{% endif %}">
|
|
{{ log.severity|default('info') }}
|
|
</span>
|
|
{{ log.resolved_message[:100] }}{% if log.resolved_message|length > 100 %}...{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="5" class="text-center text-muted py-4">
|
|
<i class="fas fa-inbox fa-2x"></i><br>
|
|
No recent logs available
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if recent_logs|length >= 50 %}
|
|
<div class="text-center mt-3">
|
|
<a href="{{ url_for('main.logs') }}" class="btn btn-outline-primary">
|
|
<i class="fas fa-arrow-right"></i> View All Logs
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Compression Statistics -->
|
|
{% if compression_stats %}
|
|
<div class="activity-section">
|
|
<h4><i class="fas fa-compress-arrows-alt"></i> Compression Statistics</h4>
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title">{{ compression_stats.get('average_ratio', 0) | round(1) }}%</h5>
|
|
<p class="card-text text-muted">Compression Ratio</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title">{{ compression_stats.get('total_saved_bytes', 0) | filesizeformat }}</h5>
|
|
<p class="card-text text-muted">Space Saved</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title">{{ compression_stats.get('template_count', 0) }}</h5>
|
|
<p class="card-text text-muted">Active Templates</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Countdown timer for refresh
|
|
let countdown = 30; // 30 seconds
|
|
|
|
function updateTimer() {
|
|
const timerElement = document.getElementById('refresh-timer');
|
|
if (timerElement) {
|
|
timerElement.innerText = countdown;
|
|
}
|
|
countdown--;
|
|
if (countdown < 0) {
|
|
location.reload(); // Refresh the page
|
|
}
|
|
}
|
|
|
|
// Start the timer immediately
|
|
setInterval(updateTimer, 1000); // Update every second
|
|
|
|
// Database reset functionality
|
|
async function resetDatabase(event) {
|
|
try {
|
|
// First confirmation
|
|
const confirmed = confirm(
|
|
'⚠️ WARNING: Database Reset Operation ⚠️\n\n' +
|
|
'This will permanently delete ALL logs and device history!\n\n' +
|
|
'This action cannot be undone!\n\n' +
|
|
'Are you sure you want to proceed?'
|
|
);
|
|
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
// Second confirmation for safety
|
|
const doubleConfirmed = confirm(
|
|
'🚨 FINAL CONFIRMATION 🚨\n\n' +
|
|
'You are about to permanently DELETE all data!\n\n' +
|
|
'This is your LAST CHANCE to cancel!\n\n' +
|
|
'Click OK to proceed with deletion.'
|
|
);
|
|
|
|
if (!doubleConfirmed) {
|
|
return;
|
|
}
|
|
|
|
// Show loading indicator
|
|
const button = event.target;
|
|
const originalText = button.innerHTML;
|
|
button.innerHTML = '<span class="spinner-border spinner-border-sm" role="status"></span> Clearing Database...';
|
|
button.disabled = true;
|
|
|
|
// Send reset request (this would need to be implemented on the backend)
|
|
try {
|
|
const response = await fetch('/api/reset-database', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert('✅ Database cleared successfully!');
|
|
location.reload(); // Refresh to show empty database
|
|
} else {
|
|
throw new Error('Server error: ' + response.statusText);
|
|
}
|
|
} catch (fetchError) {
|
|
alert('❌ Error clearing database: ' + fetchError.message);
|
|
}
|
|
|
|
} catch (error) {
|
|
alert('❌ Network Error: ' + error.message);
|
|
} finally {
|
|
// Restore button
|
|
if (event.target) {
|
|
event.target.innerHTML = '<i class="fas fa-trash-alt"></i> Clear Database';
|
|
event.target.disabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize page
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('Dashboard loaded successfully');
|
|
|
|
// Add hover effects to stat cards
|
|
const statCards = document.querySelectorAll('.stat-card');
|
|
statCards.forEach(card => {
|
|
card.addEventListener('mouseenter', function() {
|
|
this.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.15)';
|
|
});
|
|
|
|
card.addEventListener('mouseleave', function() {
|
|
this.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.1)';
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %} |