Files
server-manager/views/servers/show.php
2026-06-06 18:08:47 -04:00

376 lines
14 KiB
PHP
Executable File

<?php
$server = $server ?? [];
$metrics = $metrics ?? null;
$historicalMetrics = $historicalMetrics ?? [];
?>
<div class="page-header">
<div class="page-header-left">
<div class="back-link">
<a href="/servers"><i class="fas fa-arrow-left"></i> Back to Servers</a>
</div>
<h1 class="page-title"><?= htmlspecialchars($server['name'] ?? 'Unknown') ?></h1>
<p class="page-subtitle">
<?= htmlspecialchars($server['ip_address'] ?? '') ?>:<?= $server['ssh_port'] ?? 22 ?>
&middot;
<span class="status-badge status-<?= $server['current_status'] ?? 'unknown' ?>">
<?= ucfirst($server['current_status'] ?? 'unknown') ?>
</span>
</p>
</div>
<div class="page-header-right">
<div class="btn-group">
<a href="/terminal/<?= $server['id'] ?>" class="btn btn-dark">
<i class="fas fa-terminal"></i> Terminal
</a>
<a href="/servers/<?= $server['id'] ?>/edit" class="btn btn-secondary">
<i class="fas fa-edit"></i> Edit
</a>
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle">
<i class="fas fa-cog"></i> Actions
</button>
<div class="dropdown-menu">
<button onclick="serverAction('reboot')" class="dropdown-item">
<i class="fas fa-redo"></i> Reboot
</button>
<button onclick="serverAction('shutdown')" class="dropdown-item text-danger">
<i class="fas fa-power-off"></i> Shutdown
</button>
<div class="dropdown-divider"></div>
<button onclick="serverAction('test-connection')" class="dropdown-item">
<i class="fas fa-plug"></i> Test Connection
</button>
</div>
</div>
</div>
</div>
</div>
<?php if ($server['description'] ?? ''): ?>
<div class="card card-description">
<div class="card-body">
<p><?= nl2br(htmlspecialchars($server['description'])) ?></p>
</div>
</div>
<?php endif; ?>
<div class="stats-grid">
<div class="stat-card stat-card-info">
<div class="stat-icon"><i class="fas fa-microchip"></i></div>
<div class="stat-info">
<span class="stat-value" id="cpuMetric"><?= $metrics['cpu_usage'] ?? '-' ?>%</span>
<span class="stat-label">CPU Usage</span>
</div>
</div>
<div class="stat-card stat-card-warning">
<div class="stat-icon"><i class="fas fa-memory"></i></div>
<div class="stat-info">
<span class="stat-value" id="ramMetric"><?= $metrics['ram_usage'] ?? '-' ?>%</span>
<span class="stat-label">RAM Usage</span>
</div>
</div>
<div class="stat-card stat-card-purple">
<div class="stat-icon"><i class="fas fa-hdd"></i></div>
<div class="stat-info">
<span class="stat-value" id="diskMetric"><?= $metrics['disk_usage'] ?? '-' ?>%</span>
<span class="stat-label">Disk Usage</span>
</div>
</div>
<div class="stat-card stat-card-primary">
<div class="stat-icon"><i class="fas fa-tachometer-alt"></i></div>
<div class="stat-info">
<span class="stat-value" id="loadMetric"><?= $metrics['load_average'] ?? '-' ?></span>
<span class="stat-label">Load Average</span>
</div>
</div>
<div class="stat-card stat-card-success">
<div class="stat-icon"><i class="fas fa-clock"></i></div>
<div class="stat-info">
<span class="stat-value" id="uptimeMetric"><?= htmlspecialchars($metrics['uptime'] ?? '-') ?></span>
<span class="stat-label">Uptime</span>
</div>
</div>
</div>
<div class="dashboard-grid">
<div class="dashboard-card">
<div class="card-header">
<h2><i class="fas fa-chart-line"></i> Resource Usage (24h)</h2>
</div>
<div class="card-body">
<canvas id="metricsChart" height="300"></canvas>
</div>
</div>
<div class="dashboard-card">
<div class="card-header">
<h2><i class="fas fa-info-circle"></i> Connection Details</h2>
</div>
<div class="card-body">
<dl class="details-list">
<dt>Host</dt>
<dd><code><?= htmlspecialchars($server['ip_address'] ?? '') ?></code></dd>
<dt>SSH Port</dt>
<dd><?= $server['ssh_port'] ?? 22 ?></dd>
<dt>SSH User</dt>
<dd><?= htmlspecialchars($server['ssh_user'] ?? '') ?></dd>
<dt>Auth Method</dt>
<dd>
<?php if (!empty($server['ssh_key'])): ?>
<span class="badge badge-info">Key-based</span>
<?php else: ?>
<span class="badge badge-warning">Password</span>
<?php endif; ?>
</dd>
<dt>Group</dt>
<dd><?= htmlspecialchars($server['group_name'] ?? 'None') ?></dd>
<dt>Last Check</dt>
<dd><?= $server['last_check_at'] ?? 'Never' ?></dd>
</dl>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h2><i class="fas fa-tools"></i> Management Actions</h2>
</div>
<div class="card-body">
<div class="action-grid">
<a href="/terminal/<?= $server['id'] ?>" class="action-card">
<i class="fas fa-terminal"></i>
<span>Web Terminal</span>
</a>
<a href="/servers/<?= $server['id'] ?>/processes" class="action-card">
<i class="fas fa-tasks"></i>
<span>Processes</span>
</a>
<a href="/servers/<?= $server['id'] ?>/system-info" class="action-card">
<i class="fas fa-info-circle"></i>
<span>System Info</span>
</a>
<a href="/servers/<?= $server['id'] ?>/logs" class="action-card">
<i class="fas fa-file-alt"></i>
<span>System Logs</span>
</a>
<a href="/servers/<?= $server['id'] ?>/nginx" class="action-card">
<svg viewBox="0 0 24 24" fill="currentColor" width="24" height="24" style="opacity:0.8">
<path d="M12 2L2 7.2v9.6L12 22l10-5.2V7.2L12 2zm0 1.5l8.5 4.4v8.2L12 20.5l-8.5-4.4V7.9L12 3.5zM7.4 8.5v7l1.5.9V11l3.1 4.3 1.5.9V8.5l-1.5.9v4.4L8.9 9.4l-1.5-.9zm9.2 0l-1.5.9-3.1 4.4V17l1.5-.9V11l3.1-4.3z"/>
</svg>
<span>Nginx</span>
</a>
<a href="/servers/<?= $server['id'] ?>/ssl" class="action-card">
<i class="fas fa-shield-alt"></i>
<span>SSL Certificates</span>
</a>
<a href="/servers/<?= $server['id'] ?>/database" class="action-card">
<i class="fas fa-database"></i>
<span>Database Manager</span>
</a>
<a href="/servers/<?= $server['id'] ?>/users" class="action-card">
<i class="fas fa-users-cog"></i>
<span>System Users</span>
</a>
<button class="action-card" onclick="restartService()">
<i class="fas fa-sync-alt"></i>
<span>Restart Service</span>
</button>
</div>
</div>
</div>
<form id="csrfForm" style="display:none">
<input type="hidden" name="_csrf_token" value="<?= $this->csrfToken() ?>">
</form>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
<script>
const historicalData = <?= json_encode($historicalMetrics) ?>;
let metricsChart = null;
function initChart(data) {
const container = document.getElementById('metricsChart').parentNode;
const oldCanvas = document.getElementById('metricsChart');
const newCanvas = document.createElement('canvas');
newCanvas.id = 'metricsChart';
newCanvas.height = 300;
container.replaceChild(newCanvas, oldCanvas);
const ctx = newCanvas.getContext('2d');
const labels = data.map(m => {
const d = new Date(m.created_at);
return d.getHours().toString().padStart(2, '0') + ':' + d.getMinutes().toString().padStart(2, '0');
});
metricsChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'CPU',
data: data.map(m => m.cpu_usage),
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
fill: true,
tension: 0.3,
pointRadius: 1,
},
{
label: 'RAM',
data: data.map(m => m.ram_usage),
borderColor: '#22c55e',
backgroundColor: 'rgba(34, 197, 94, 0.1)',
fill: true,
tension: 0.3,
pointRadius: 1,
},
{
label: 'Disk',
data: data.map(m => m.disk_usage),
borderColor: '#8b5cf6',
backgroundColor: 'rgba(139, 92, 246, 0.1)',
fill: true,
tension: 0.3,
pointRadius: 1,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index',
},
plugins: {
legend: {
labels: {
color: '#9ca3af',
font: { family: "'Inter', sans-serif" },
boxWidth: 12,
padding: 16,
},
},
tooltip: {
backgroundColor: '#1a1d2b',
borderColor: '#2a2d3d',
borderWidth: 1,
titleColor: '#e1e4ed',
bodyColor: '#9ca3af',
padding: 10,
cornerRadius: 8,
callbacks: {
label: function(ctx) {
return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(1) + '%';
},
},
},
},
scales: {
x: {
ticks: {
color: '#6b7280',
maxTicksLimit: 12,
font: { size: 11 },
},
grid: {
color: 'rgba(255,255,255,0.04)',
},
},
y: {
min: 0,
max: 100,
ticks: {
color: '#6b7280',
font: { size: 11 },
callback: function(v) { return v + '%'; },
},
grid: {
color: 'rgba(255,255,255,0.04)',
},
},
},
},
});
}
if (historicalData.length > 0) {
initChart(historicalData);
}
function getCsrfToken() {
return document.querySelector('meta[name="csrf-token"]')?.content || '';
}
function serverAction(action) {
if (action === 'shutdown' || action === 'reboot') {
if (!confirm('Are you sure you want to ' + action + ' this server?')) return;
}
fetch('/servers/<?= $server['id'] ?>/' + action, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRF-Token': getCsrfToken(),
},
body: '_csrf_token=' + encodeURIComponent(getCsrfToken()),
})
.then(r => r.json())
.then(data => {
showToast(data.success ? 'success' : 'error', data.message);
})
.catch(err => showToast('error', 'Request failed: ' + err.message));
}
function restartService() {
const service = prompt('Enter service name to restart (e.g., nginx, mysql, apache2):');
if (!service) return;
fetch('/servers/<?= $server['id'] ?>/restart-service', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRF-Token': getCsrfToken(),
},
body: 'service=' + encodeURIComponent(service) + '&_csrf_token=' + encodeURIComponent(getCsrfToken()),
})
.then(r => r.json())
.then(data => {
showToast(data.success ? 'success' : 'error', data.message);
})
.catch(err => showToast('error', 'Request failed: ' + err.message));
}
function refreshMetrics() {
fetch('/servers/<?= $server['id'] ?>/metrics')
.then(r => r.json())
.then(data => {
if (data.success && data.data) {
const m = data.data;
document.getElementById('cpuMetric').textContent = (m.cpu_usage || '-') + '%';
document.getElementById('ramMetric').textContent = (m.ram_usage || '-') + '%';
document.getElementById('diskMetric').textContent = (m.disk_usage || '-') + '%';
document.getElementById('loadMetric').textContent = m.load_average || '-';
document.getElementById('uptimeMetric').textContent = m.uptime || '-';
if (metricsChart && metricsChart.data.labels.length > 0) {
const now = new Date();
const label = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
metricsChart.data.labels.push(label);
metricsChart.data.datasets[0].data.push(m.cpu_usage);
metricsChart.data.datasets[1].data.push(m.ram_usage);
metricsChart.data.datasets[2].data.push(m.disk_usage);
if (metricsChart.data.labels.length > 48) {
metricsChart.data.labels.shift();
metricsChart.data.datasets.forEach(ds => ds.data.shift());
}
metricsChart.update('none');
}
}
});
}
setInterval(refreshMetrics, 30000);
</script>