feat: Enhanced dashboard with group colors, metric icons and AJAX update
- Accordion header color matches group color from settings - CPU/RAM/DISK now use Font Awesome icons (microchip, memory, hdd) - Icon colors change based on threshold status (red/yellow/green) - AJAX refresh updates values every 30 seconds without page reload - Added IDs to metric elements for targeted updates
This commit is contained in:
parent
7c15ed82a0
commit
3872d1df30
|
|
@ -80,7 +80,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for groupName, group in groups %}
|
{% for groupName, group in groups %}
|
||||||
<div class="card mb-3 border-0 shadow-sm">
|
<div class="card mb-3 border-0 shadow-sm">
|
||||||
<div class="card-header bg-dark text-white py-2" data-bs-toggle="collapse" data-bs-target="#group-{{ loop.index }}" style="cursor: pointer;">
|
<div class="card-header text-white py-2" data-bs-toggle="collapse" data-bs-target="#group-{{ loop.index }}" style="cursor: pointer; background-color: {{ group.color|default('#6c757d') }};">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0">
|
<h5 class="mb-0">
|
||||||
<i class="{{ group.icon|default('fa-server') }}"></i>
|
<i class="{{ group.icon|default('fa-server') }}"></i>
|
||||||
|
|
@ -113,42 +113,53 @@
|
||||||
<span class="badge bg-danger badge-sm">{{ server.active_alerts }}</span>
|
<span class="badge bg-danger badge-sm">{{ server.active_alerts }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Метрики с иконками -->
|
||||||
<div class="compact-metrics">
|
<div class="compact-metrics">
|
||||||
{% if server.latest_metrics['cpu_load'] is defined %}
|
{% if server.latest_metrics['cpu_load'] is defined %}
|
||||||
<div class="metric-mini">
|
{% set cpu_val = server.latest_metrics['cpu_load'].value %}
|
||||||
<span class="text-muted">CPU</span>
|
{% set cpu_t = server.thresholds['cpu_load']|default(null) %}
|
||||||
<div class="progress-micro mb-1">
|
{% if cpu_t %}
|
||||||
{% set cpu_val = server.latest_metrics['cpu_load'].value %}
|
{% set cpu_color_icon = cpu_val >= cpu_t.critical ? 'text-danger' : (cpu_val >= cpu_t.warning ? 'text-warning' : 'text-success') %}
|
||||||
{% set cpu_color = cpu_val > 80 ? 'bg-danger' : (cpu_val > 60 ? 'bg-warning' : 'bg-success') %}
|
{% else %}
|
||||||
<div class="progress-bar {{ cpu_color }}" style="width: {{ cpu_val }}%"></div>
|
{% set cpu_color_icon = cpu_val > 80 ? 'text-danger' : (cpu_val > 60 ? 'text-warning' : 'text-success') %}
|
||||||
</div>
|
|
||||||
<strong>{{ cpu_val }}%</strong>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<span id="cpu-icon-{{ server.id }}" class="metric-icon {{ cpu_color_icon }}" title="CPU">
|
||||||
|
<i class="fas fa-microchip"></i>
|
||||||
|
</span>
|
||||||
|
<span id="cpu-val-{{ server.id }}" class="metric-val {{ cpu_color_icon }}">{{ cpu_val }}%</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if server.latest_metrics['ram_used'] is defined %}
|
{% if server.latest_metrics['ram_used'] is defined %}
|
||||||
<div class="metric-mini">
|
{% set ram_val = server.latest_metrics['ram_used'].value %}
|
||||||
<span class="text-muted">RAM</span>
|
{% set ram_t = server.thresholds['ram_used']|default(null) %}
|
||||||
<div class="progress-micro mb-1">
|
{% if ram_t %}
|
||||||
{% set ram_val = server.latest_metrics['ram_used'].value %}
|
{% set ram_color_icon = ram_val >= ram_t.critical ? 'text-danger' : (ram_val >= ram_t.warning ? 'text-warning' : 'text-success') %}
|
||||||
{% set ram_color = ram_val > 80 ? 'bg-danger' : (ram_val > 60 ? 'bg-warning' : 'bg-success') %}
|
{% else %}
|
||||||
<div class="progress-bar {{ ram_color }}" style="width: {{ ram_val }}%"></div>
|
{% set ram_color_icon = ram_val > 80 ? 'text-danger' : (ram_val > 60 ? 'text-warning' : 'text-success') %}
|
||||||
</div>
|
|
||||||
<strong>{{ ram_val }}%</strong>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<span id="ram-icon-{{ server.id }}" class="metric-icon {{ ram_color_icon }}" title="RAM">
|
||||||
|
<i class="fas fa-memory"></i>
|
||||||
|
</span>
|
||||||
|
<span id="ram-val-{{ server.id }}" class="metric-val {{ ram_color_icon }}">{{ ram_val }}%</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% set disk_metric = server.latest_metrics['disk_used_root'] ?? server.latest_metrics['disk_used'] ?? null %}
|
{% set disk_metric = server.latest_metrics['disk_used_root'] ?? server.latest_metrics['disk_used'] ?? null %}
|
||||||
{% if disk_metric is not null %}
|
{% if disk_metric is not null %}
|
||||||
<div class="metric-mini">
|
{% set disk_val = disk_metric.value %}
|
||||||
<span class="text-muted">DISK</span>
|
{% set disk_t = server.thresholds['disk_used_root']|default(null) %}
|
||||||
<div class="progress-micro mb-1">
|
{% if disk_t %}
|
||||||
{% set disk_val = disk_metric.value %}
|
{% set disk_color_icon = disk_val >= disk_t.critical ? 'text-danger' : (disk_val >= disk_t.warning ? 'text-warning' : 'text-success') %}
|
||||||
{% set disk_color = disk_val > 90 ? 'bg-danger' : (disk_val > 75 ? 'bg-warning' : 'bg-success') %}
|
{% else %}
|
||||||
<div class="progress-bar {{ disk_color }}" style="width: {{ disk_val }}%"></div>
|
{% set disk_color_icon = disk_val > 90 ? 'text-danger' : (disk_val > 75 ? 'text-warning' : 'text-success') %}
|
||||||
</div>
|
{% endif %}
|
||||||
<strong>{{ disk_val }}%</strong>
|
<span id="disk-icon-{{ server.id }}" class="metric-icon {{ disk_color_icon }}" title="Disk">
|
||||||
</div>
|
<i class="fas fa-hdd"></i>
|
||||||
|
</span>
|
||||||
|
<span id="disk-val-{{ server.id }}" class="metric-val {{ disk_color_icon }}">{{ disk_val }}%</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if server.latest_metrics['uptime'] is defined %}
|
{% if server.latest_metrics['uptime'] is defined %}
|
||||||
<div class="uptime-mini text-muted small mt-1">
|
<div class="uptime-mini text-muted small mt-1">
|
||||||
<i class="fas fa-clock"></i>
|
<i class="fas fa-clock"></i>
|
||||||
|
|
@ -162,6 +173,15 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Время обновления (для AJAX) -->
|
||||||
|
<div class="text-muted small updated-at" id="updated-at-{{ server.id }}">
|
||||||
|
{% if server.last_metrics_at %}
|
||||||
|
{{ server.last_metrics_at|date('H:i') }}
|
||||||
|
{% else %}
|
||||||
|
—
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -173,9 +193,49 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Автообновление -->
|
<!-- AJAX автообновление -->
|
||||||
<script>
|
<script>
|
||||||
setTimeout(function() { location.reload(); }, 30000);
|
(function() {
|
||||||
|
let updateInterval = null;
|
||||||
|
|
||||||
|
function updateDashboard() {
|
||||||
|
fetch('/api/dashboard/stats')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
data.forEach(server => {
|
||||||
|
// CPU
|
||||||
|
const cpuVal = document.getElementById('cpu-val-' + server.id);
|
||||||
|
if (cpuVal && server.metrics.cpu_load) {
|
||||||
|
cpuVal.textContent = server.metrics.cpu_load.value + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAM
|
||||||
|
const ramVal = document.getElementById('ram-val-' + server.id);
|
||||||
|
if (ramVal && server.metrics.ram_used) {
|
||||||
|
ramVal.textContent = server.metrics.ram_used.value + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disk
|
||||||
|
const diskVal = document.getElementById('disk-val-' + server.id);
|
||||||
|
if (diskVal && server.metrics.disk) {
|
||||||
|
diskVal.textContent = server.metrics.disk.value + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updated time
|
||||||
|
const updatedAt = document.getElementById('updated-at-' + server.id);
|
||||||
|
if (updatedAt) {
|
||||||
|
updatedAt.textContent = server.updated_at.split(' ')[1].substring(0, 5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => console.log('Dashboard update error:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запускаем обновление каждые 30 секунд
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
updateInterval = setInterval(updateDashboard, 30000);
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -206,24 +266,20 @@
|
||||||
}
|
}
|
||||||
.compact-metrics {
|
.compact-metrics {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
.metric-mini {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 50px;
|
|
||||||
}
|
|
||||||
.metric-mini span {
|
|
||||||
font-size: 0.65rem;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.metric-mini strong {
|
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.progress-micro {
|
.metric-icon {
|
||||||
height: 4px;
|
font-size: 0.9rem;
|
||||||
border-radius: 2px;
|
|
||||||
}
|
}
|
||||||
|
.metric-val {
|
||||||
|
font-weight: 600;
|
||||||
|
min-width: 35px;
|
||||||
|
}
|
||||||
|
.metric-icon.text-danger, .metric-val.text-danger { color: #dc3545 !important; }
|
||||||
|
.metric-icon.text-warning, .metric-val.text-warning { color: #ffc107 !important; }
|
||||||
|
.metric-icon.text-success, .metric-val.text-success { color: #28a745 !important; }
|
||||||
.badge-sm {
|
.badge-sm {
|
||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
padding: 0.15em 0.4em;
|
padding: 0.15em 0.4em;
|
||||||
|
|
@ -231,5 +287,9 @@
|
||||||
.uptime-mini i {
|
.uptime-mini i {
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
}
|
}
|
||||||
|
.updated-at {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue