Files

182 lines
6.7 KiB
Twig

{% extends "layout.twig" %}
{% block title %}{{ t('servers.title') }}{% endblock %}
{% block styles %}
<style>
.server-metrics {
display: flex;
gap: 12px;
font-size: 0.75rem;
margin-top: 4px;
}
.metric-badge {
display: flex;
align-items: center;
gap: 4px;
padding: 2px 6px;
background: #f3f4f6;
border-radius: 4px;
white-space: nowrap;
}
.metric-badge i {
font-size: 10px;
}
.metric-sparkline {
height: 20px;
width: 40px;
display: inline-block;
}
</style>
{% endblock %}
{% block content %}
<div class="max-w-7xl mx-auto px-4 py-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold">{{ t('servers.title') }}</h1>
<a href="/servers/create" class="gradient-bg text-white px-4 py-2 rounded"><i class="fas fa-plus"></i> {{ t('servers.add') }}</a>
</div>
{% if session.success_message %}
<div class="mb-4 bg-green-50 border-l-4 border-green-400 p-4">
<p class="text-sm text-green-700"><i class="fas fa-check-circle mr-2"></i>{{ session.success_message }}</p>
</div>
{% endif %}
{% if session.error_message %}
<div class="mb-4 bg-red-50 border-l-4 border-red-400 p-4">
<p class="text-sm text-red-700"><i class="fas fa-exclamation-circle mr-2"></i>{{ session.error_message }}</p>
</div>
{% endif %}
{% if servers|length > 0 %}
<div class="bg-white rounded shadow overflow-hidden">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('servers.name') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('servers.host') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('servers.status') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('common.metrics') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('servers.actions') }}</th>
</tr>
</thead>
<tbody>
{% for server in servers %}
<tr class="border-t" data-server-id="{{ server.id }}">
<td class="px-6 py-4 font-medium">{{ server.name }}</td>
<td class="px-6 py-4">{{ server.host }}</td>
<td class="px-6 py-4">
<span class="px-2 py-1 bg-{{ server.status == 'active' ? 'green' : 'yellow' }}-100 text-{{ server.status == 'active' ? 'green' : 'yellow' }}-800 rounded text-sm">
{{ server.status }}
</span>
</td>
<td class="px-6 py-4">
{% if server.status == 'active' %}
<div class="server-metrics" id="metrics-{{ server.id }}">
<div class="metric-badge">
<i class="fas fa-microchip" style="color:#dc3545"></i>
<span class="metric-val" data-metric="cpu">--</span>
</div>
<div class="metric-badge">
<i class="fas fa-memory" style="color:#ffc107"></i>
<span class="metric-val" data-metric="ram">--</span>
</div>
<div class="metric-badge">
<i class="fas fa-hdd" style="color:#17a2b8"></i>
<span class="metric-val" data-metric="disk">--</span>
</div>
<div class="metric-badge">
<i class="fas fa-network-wired" style="color:#28a745"></i>
<span class="metric-val" data-metric="network">--</span>
</div>
</div>
{% else %}
<span class="text-gray-400 text-xs">Not deployed</span>
{% endif %}
</td>
<td class="px-6 py-4 space-x-3">
<a href="/servers/{{ server.id }}" class="text-purple-600 hover:text-purple-900">
<i class="fas fa-eye mr-1"></i>{{ t('servers.view') }}
</a>
<form method="POST" action="/servers/{{ server.id }}/delete" class="inline" id="delete-form-{{ server.id }}">
<button type="button" class="text-red-600 hover:text-red-900" onclick="(async()=>{ event.stopPropagation(); if(await showConfirmModal('Удалить сервер {{ server.name }}?', 'Удаление сервера')) { document.getElementById('delete-form-{{ server.id }}').submit(); } })()">
<i class="fas fa-trash mr-1"></i>{{ t('servers.delete') }}
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="bg-white rounded shadow p-12 text-center">
<p class="text-gray-500 mb-4">{{ t('dashboard.no_servers') }}</p>
<a href="/servers/create" class="gradient-bg text-white px-6 py-3 rounded inline-block">{{ t('dashboard.add_first_server') }}</a>
</div>
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<script>
// Get all active server IDs
const activeServers = [
{% for server in servers %}
{% if server.status == 'active' %}
{{ server.id }},
{% endif %}
{% endfor %}
];
async function updateServerMetrics(serverId) {
try {
const response = await fetch(`/api/servers/${serverId}/metrics?hours=1`, {
credentials: 'same-origin'
});
const data = await response.json();
console.log(`Server ${serverId} metrics:`, data);
if (data.success && data.metrics.length > 0) {
const latest = data.metrics[data.metrics.length - 1];
const metricsDiv = document.getElementById(`metrics-${serverId}`);
console.log(`Server ${serverId} latest:`, latest);
if (metricsDiv) {
const cpuVal = parseFloat(latest.cpu_percent).toFixed(1);
metricsDiv.querySelector('[data-metric="cpu"]').textContent = `${cpuVal}%`;
const ramPercent = (latest.ram_used_mb / latest.ram_total_mb * 100).toFixed(0);
metricsDiv.querySelector('[data-metric="ram"]').textContent = `${ramPercent}%`;
const diskPercent = (latest.disk_used_gb / latest.disk_total_gb * 100).toFixed(0);
metricsDiv.querySelector('[data-metric="disk"]').textContent = `${diskPercent}%`;
const netRx = parseFloat(latest.network_rx_mbps).toFixed(1);
const netTx = parseFloat(latest.network_tx_mbps).toFixed(1);
metricsDiv.querySelector('[data-metric="network"]').textContent = `↓${netRx}↑${netTx}`;
}
}
} catch (error) {
console.error(`Failed to fetch metrics for server ${serverId}:`, error);
}
}
function updateAllMetrics() {
activeServers.forEach(serverId => {
updateServerMetrics(serverId);
});
}
// Initial load
if (activeServers.length > 0) {
updateAllMetrics();
// Update every 30 seconds
setInterval(updateAllMetrics, 30000);
}
</script>
{% endblock %}