222 lines
8.8 KiB
HTML
222 lines
8.8 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ computer.hostname }} — InfoUser Monitor{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2>{{ computer.hostname }}</h2>
|
|
<a href="/" class="btn btn-outline-secondary">← Назад</a>
|
|
</div>
|
|
|
|
<div class="row g-4 mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card text-center shadow-sm">
|
|
<div class="card-body">
|
|
<div class="text-muted small">Статус</div>
|
|
<span class="badge {% if computer.status == 'online' %}bg-success{% else %}bg-secondary{% endif %} fs-6">
|
|
{{ computer.status }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center shadow-sm">
|
|
<div class="card-body">
|
|
<div class="text-muted small">Пользователь</div>
|
|
{% if computer.current_user and ',' in computer.current_user %}
|
|
<span class="badge bg-info text-dark" title="{{ computer.current_user }}">за ПК работают {{ computer.current_user.split(',') | length }} пользователя</span>
|
|
{% else %}
|
|
<div class="fw-bold">{{ computer.current_user or '-' }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center shadow-sm">
|
|
<div class="card-body">
|
|
<div class="text-muted small">IP</div>
|
|
<div class="fw-bold">{{ computer.current_ip or '-' }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center shadow-sm">
|
|
<div class="card-body">
|
|
<div class="text-muted small">OS</div>
|
|
<div class="fw-bold">{{ computer.os_info or '-' }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4 mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card text-center shadow-sm">
|
|
<div class="card-body">
|
|
<div class="text-muted small">RAM</div>
|
|
<div class="fw-bold">
|
|
{% if computer.current_ram_used_gb is not none and computer.current_ram_total_gb is not none %}
|
|
{{ computer.current_ram_used_gb | round(1) }} / {{ computer.current_ram_total_gb | round(1) }} GB
|
|
<span class="text-muted small">({{ computer.current_ram_percent | round(1) if computer.current_ram_percent is not none else '-' }}%)</span>
|
|
{% else %}
|
|
{{ computer.current_ram_percent | round(1) if computer.current_ram_percent is not none else '-' }}%
|
|
{% endif %}
|
|
</div>
|
|
{% if computer.current_ram_available_gb is not none %}
|
|
<div class="text-muted small">доступно {{ computer.current_ram_available_gb | round(1) }} GB</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card text-center shadow-sm">
|
|
<div class="card-body">
|
|
<div class="text-muted small">Swap</div>
|
|
<div class="fw-bold">
|
|
{% if computer.current_swap_used_gb is not none and computer.current_swap_total_gb is not none %}
|
|
{{ computer.current_swap_used_gb | round(1) }} / {{ computer.current_swap_total_gb | round(1) }} GB
|
|
<span class="text-muted small">({{ computer.current_swap_percent | round(1) if computer.current_swap_percent is not none else '-' }}%)</span>
|
|
{% else %}
|
|
{{ computer.current_swap_percent | round(1) if computer.current_swap_percent is not none else '-' }}%
|
|
{% endif %}
|
|
</div>
|
|
{% if computer.current_swap_free_gb is not none %}
|
|
<div class="text-muted small">свободно {{ computer.current_swap_free_gb | round(1) }} GB</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Активные сессии</h5>
|
|
{% if latest_sessions %}
|
|
<table class="table table-sm mb-0">
|
|
<thead>
|
|
<tr><th>Пользователь</th><th>TTY</th><th>Время входа</th><th>Откуда</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for s in latest_sessions %}
|
|
<tr>
|
|
<td>{{ s.username or '-' }}</td>
|
|
<td>{{ s.tty or '-' }}</td>
|
|
<td>{{ s.login_time or '-' }}</td>
|
|
<td>{{ s.origin or '-' }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<div class="text-muted">Нет данных о сессиях.</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4 mb-4">
|
|
<div class="col-md-4">
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<h5 class="card-title">CPU, %</h5>
|
|
<canvas id="cpuChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<h5 class="card-title">RAM, %</h5>
|
|
<canvas id="ramChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Swap, %</h5>
|
|
<canvas id="swapChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Последние процессы</h5>
|
|
{% if heartbeats and heartbeats[-1].processes %}
|
|
<table class="table table-sm">
|
|
<thead><tr><th>PID</th><th>Имя</th><th>CPU%</th><th>RAM%</th></tr></thead>
|
|
<tbody>
|
|
{% for p in heartbeats[-1].processes[:10] %}
|
|
<tr>
|
|
<td>{{ p.pid }}</td>
|
|
<td>{{ p.name }}</td>
|
|
<td>{{ p.cpu_percent | round(1) }}</td>
|
|
<td>{{ p.ram_percent | round(1) }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<div class="text-muted">Нет данных о процессах.</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<h5 class="card-title">История heartbeats</h5>
|
|
<table class="table table-sm">
|
|
<thead><tr><th>Время</th><th>CPU</th><th>RAM</th><th>RAM GB</th><th>Swap</th><th>IP</th></tr></thead>
|
|
<tbody>
|
|
{% for h in heartbeats[-20:] | reverse %}
|
|
<tr>
|
|
<td>{{ h.timestamp[:16] | replace('T', ' ') if h.timestamp else '-' }}</td>
|
|
<td>{{ h.cpu_percent | round(1) }}%</td>
|
|
<td>{{ h.ram_percent | round(1) }}%</td>
|
|
<td>
|
|
{% if h.ram_used_gb is not none and h.ram_total_gb is not none %}
|
|
{{ h.ram_used_gb | round(1) }} / {{ h.ram_total_gb | round(1) }}
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ h.swap_percent | round(1) if h.swap_percent is not none else '-' }}%</td>
|
|
<td>{{ h.local_ip or '-' }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
const heartbeats = {{ heartbeats | tojson }};
|
|
const labels = heartbeats.map(h => h.timestamp.replace('T', ' ').slice(0, 16));
|
|
const cpuData = heartbeats.map(h => h.cpu_percent ?? null);
|
|
const ramData = heartbeats.map(h => h.ram_percent ?? null);
|
|
const swapData = heartbeats.map(h => h.swap_percent ?? null);
|
|
|
|
new Chart(document.getElementById('cpuChart'), {
|
|
type: 'line',
|
|
data: { labels, datasets: [{ label: 'CPU %', data: cpuData, borderColor: 'rgb(255, 99, 132)', tension: 0.2 }] },
|
|
options: { scales: { y: { beginAtZero: true, max: 100 } }, spanGaps: true }
|
|
});
|
|
|
|
new Chart(document.getElementById('ramChart'), {
|
|
type: 'line',
|
|
data: { labels, datasets: [{ label: 'RAM %', data: ramData, borderColor: 'rgb(54, 162, 235)', tension: 0.2 }] },
|
|
options: { scales: { y: { beginAtZero: true, max: 100 } }, spanGaps: true }
|
|
});
|
|
|
|
new Chart(document.getElementById('swapChart'), {
|
|
type: 'line',
|
|
data: { labels, datasets: [{ label: 'Swap %', data: swapData, borderColor: 'rgb(255, 159, 64)', tension: 0.2 }] },
|
|
options: { scales: { y: { beginAtZero: true, max: 100 } }, spanGaps: true }
|
|
});
|
|
</script>
|
|
{% endblock %}
|