236 lines
10 KiB
Twig
Executable File
236 lines
10 KiB
Twig
Executable File
{% extends "layout.twig" %}
|
|
|
|
{% block content %}
|
|
<div class="row mb-3">
|
|
<div class="col-12 d-flex justify-content-between align-items-center">
|
|
<h2 class="mb-0"><i class="fas fa-tachometer-alt"></i> Дашборд</h2>
|
|
<a href="/servers/create" class="btn btn-primary btn-sm">
|
|
<i class="fas fa-plus"></i> Добавить
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Компактная статистика -->
|
|
<div class="row mb-3">
|
|
<div class="col-6 col-md-3 mb-2">
|
|
<div class="card border-0 shadow-sm bg-primary text-white">
|
|
<div class="card-body py-2 px-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<small>Серверов</small>
|
|
<h4 class="mb-0">{{ stats.total_servers }}</h4>
|
|
</div>
|
|
<i class="fas fa-server fa-2x opacity-50"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6 col-md-3 mb-2">
|
|
<div class="card border-0 shadow-sm bg-success text-white">
|
|
<div class="card-body py-2 px-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<small>Онлайн</small>
|
|
<h4 class="mb-0">{{ stats.servers_with_metrics }}</h4>
|
|
</div>
|
|
<i class="fas fa-check-circle fa-2x opacity-50"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6 col-md-3 mb-2">
|
|
<div class="card border-0 shadow-sm bg-warning text-dark">
|
|
<div class="card-body py-2 px-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<small>Предупреждения</small>
|
|
<h4 class="mb-0">{{ stats.warnings }}</h4>
|
|
</div>
|
|
<i class="fas fa-exclamation-triangle fa-2x opacity-50"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6 col-md-3 mb-2">
|
|
<div class="card border-0 shadow-sm bg-danger text-white">
|
|
<div class="card-body py-2 px-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<small>Критические</small>
|
|
<h4 class="mb-0">{{ stats.criticals }}</h4>
|
|
</div>
|
|
<i class="fas fa-radiation fa-2x opacity-50"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Серверы по группам -->
|
|
{% if groups|length == 0 %}
|
|
<div class="card">
|
|
<div class="card-body text-center py-5">
|
|
<i class="fas fa-server fa-4x text-muted mb-3"></i>
|
|
<h4>Серверы пока не добавлены</h4>
|
|
<a href="/servers/create" class="btn btn-primary">
|
|
<i class="fas fa-plus"></i> Добавить сервер
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
{% for groupName, group in groups %}
|
|
<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="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="{{ group.icon|default('fa-server') }}"></i>
|
|
{{ groupName }}
|
|
<span class="badge bg-light text-dark ms-2">{{ group.servers|length }}</span>
|
|
</h5>
|
|
<i class="fas fa-chevron-down"></i>
|
|
</div>
|
|
</div>
|
|
<div id="group-{{ loop.index }}" class="collapse show">
|
|
<div class="card-body p-2">
|
|
<div class="row g-2">
|
|
{% for server in group.servers %}
|
|
<div class="col-6 col-lg-4 col-xl-3">
|
|
<a href="/servers/{{ server.id }}" class="text-decoration-none">
|
|
<div class="server-card compact p-2 rounded
|
|
{% if server.status == 'offline' %}border-danger{% elseif server.status == 'warning' %}border-warning{% else %}border-success{% endif %}">
|
|
<div class="d-flex justify-content-between align-items-start mb-1">
|
|
<div class="d-flex align-items-center">
|
|
{% if server.status == 'online' %}
|
|
<span class="status-dot bg-success me-2"></span>
|
|
{% elseif server.status == 'warning' %}
|
|
<span class="status-dot bg-warning me-2"></span>
|
|
{% else %}
|
|
<span class="status-dot bg-danger me-2"></span>
|
|
{% endif %}
|
|
<strong class="server-name">{{ server.name }}</strong>
|
|
</div>
|
|
{% if server.active_alerts > 0 %}
|
|
<span class="badge bg-danger badge-sm">{{ server.active_alerts }}</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="compact-metrics">
|
|
{% if server.latest_metrics['cpu_load'] is defined %}
|
|
<div class="metric-mini">
|
|
<span class="text-muted">CPU</span>
|
|
<div class="progress-micro mb-1">
|
|
{% set cpu_val = server.latest_metrics['cpu_load'].value %}
|
|
{% set cpu_color = cpu_val > 80 ? 'bg-danger' : (cpu_val > 60 ? 'bg-warning' : 'bg-success') %}
|
|
<div class="progress-bar {{ cpu_color }}" style="width: {{ cpu_val }}%"></div>
|
|
</div>
|
|
<strong>{{ cpu_val }}%</strong>
|
|
</div>
|
|
{% endif %}
|
|
{% if server.latest_metrics['ram_used'] is defined %}
|
|
<div class="metric-mini">
|
|
<span class="text-muted">RAM</span>
|
|
<div class="progress-micro mb-1">
|
|
{% set ram_val = server.latest_metrics['ram_used'].value %}
|
|
{% set ram_color = ram_val > 80 ? 'bg-danger' : (ram_val > 60 ? 'bg-warning' : 'bg-success') %}
|
|
<div class="progress-bar {{ ram_color }}" style="width: {{ ram_val }}%"></div>
|
|
</div>
|
|
<strong>{{ ram_val }}%</strong>
|
|
</div>
|
|
{% endif %}
|
|
{% set disk_metric = server.latest_metrics['disk_used_root'] ?? server.latest_metrics['disk_used'] ?? null %}
|
|
{% if disk_metric is not null %}
|
|
<div class="metric-mini">
|
|
<span class="text-muted">DISK</span>
|
|
<div class="progress-micro mb-1">
|
|
{% set disk_val = disk_metric.value %}
|
|
{% set disk_color = disk_val > 90 ? 'bg-danger' : (disk_val > 75 ? 'bg-warning' : 'bg-success') %}
|
|
<div class="progress-bar {{ disk_color }}" style="width: {{ disk_val }}%"></div>
|
|
</div>
|
|
<strong>{{ disk_val }}%</strong>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% if server.latest_metrics['uptime'] is defined %}
|
|
<div class="uptime-mini text-muted small mt-1">
|
|
<i class="fas fa-clock"></i>
|
|
{% set uptime_sec = server.latest_metrics['uptime'].value %}
|
|
{% if uptime_sec >= 86400 %}
|
|
{{ (uptime_sec / 86400)|round(0, 'floor') }}д {{ ((uptime_sec % 86400) / 3600)|round(0, 'floor') }}ч
|
|
{% elseif uptime_sec >= 3600 %}
|
|
{{ (uptime_sec / 3600)|round(0, 'floor') }}ч {{ ((uptime_sec % 3600) / 60)|round(0, 'floor') }}м
|
|
{% else %}
|
|
{{ (uptime_sec / 60)|round(0, 'floor') }}м
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</a>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
<!-- Автообновление -->
|
|
<script>
|
|
setTimeout(function() { location.reload(); }, 30000);
|
|
</script>
|
|
|
|
<style>
|
|
.status-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
}
|
|
.server-card.compact {
|
|
background: #f8f9fa;
|
|
border-left: 4px solid #28a745;
|
|
transition: all 0.2s;
|
|
}
|
|
.server-card.compact:hover {
|
|
background: #e9ecef;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
}
|
|
.server-card.border-warning { border-left-color: #ffc107 !important; }
|
|
.server-card.border-danger { border-left-color: #dc3545 !important; }
|
|
.server-name {
|
|
font-size: 0.9rem;
|
|
max-width: 120px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.compact-metrics {
|
|
display: flex;
|
|
gap: 8px;
|
|
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;
|
|
}
|
|
.progress-micro {
|
|
height: 4px;
|
|
border-radius: 2px;
|
|
}
|
|
.badge-sm {
|
|
font-size: 0.65rem;
|
|
padding: 0.15em 0.4em;
|
|
}
|
|
.uptime-mini i {
|
|
font-size: 0.7rem;
|
|
}
|
|
</style>
|
|
{% endblock %}
|