257edb8226
- Added a new PHP script for collecting server metrics every 30 seconds. - Created a ServerMonitoring class to handle metrics collection for CPU, RAM, Disk, and Network. - Introduced database tables for storing server and client metrics. - Updated server view template to display real-time metrics using Chart.js. - Added translations for monitoring UI elements. - Created a new monitoring template for detailed server metrics visualization. - Implemented client speed tracking and display in the monitoring UI.
183 lines
6.5 KiB
Twig
183 lines
6.5 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"
|
|
onsubmit="return confirm('{{ t('message.confirm') }} Delete server {{ server.name }}?');">
|
|
<button type="submit" class="text-red-600 hover:text-red-900">
|
|
<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 %}
|