698 lines
29 KiB
PHP
Executable File
698 lines
29 KiB
PHP
Executable File
<?php
|
|
$server = $server ?? [];
|
|
$metrics = $metrics ?? null;
|
|
$historicalMetrics = $historicalMetrics ?? [];
|
|
$server_role = $server_role ?? null;
|
|
$canManage = $server_role === 'owner' || $server_role === 'manager';
|
|
?>
|
|
|
|
<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 ?>
|
|
·
|
|
<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">
|
|
<span class="badge badge-<?= $server_role === 'owner' || $server_role === 'manager' ? 'warning' : 'info' ?>" style="align-self:center">
|
|
<?= htmlspecialchars(ucfirst($server_role ?? 'viewer')) ?>
|
|
</span>
|
|
<a href="/terminal/<?= $server['id'] ?>" class="btn btn-dark">
|
|
<i class="fas fa-terminal"></i> Terminal
|
|
</a>
|
|
<?php if ($canManage): ?>
|
|
<a href="/servers/<?= $server['id'] ?>/edit" class="btn btn-secondary">
|
|
<i class="fas fa-edit"></i> Edit
|
|
</a>
|
|
<?php endif; ?>
|
|
<?php if ($canManage): ?>
|
|
<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>
|
|
<?php endif; ?>
|
|
</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 class="dashboard-card">
|
|
<div class="card-header">
|
|
<h2><i class="fas fa-wifi"></i> Monitoring Agent</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<?php if (!empty($server['agent_key'])): ?>
|
|
<dl class="details-list">
|
|
<dt>Agent Key</dt>
|
|
<dd>
|
|
<code id="agentKeyDisplay"><?= substr($server['agent_key'], 0, 12) ?>...<?= substr($server['agent_key'], -4) ?></code>
|
|
<button class="btn btn-sm btn-secondary" onclick="copyAgentKey()" title="Copy full key">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
<input type="hidden" id="agentKeyFull" value="<?= htmlspecialchars($server['agent_key']) ?>">
|
|
</dd>
|
|
<dt>Status</dt>
|
|
<dd>
|
|
<?php if ($server['agent_installed'] ?? false): ?>
|
|
<span class="badge badge-success">Installed</span>
|
|
<?php else: ?>
|
|
<span class="badge badge-secondary">Not installed</span>
|
|
<?php endif; ?>
|
|
</dd>
|
|
<?php if (!empty($server['agent_install_path'])): ?>
|
|
<dt>Install Path</dt>
|
|
<dd><code><?= htmlspecialchars($server['agent_install_path']) ?></code></dd>
|
|
<?php endif; ?>
|
|
<dt>Last Seen</dt>
|
|
<dd><?= $server['agent_last_seen_at'] ?? 'Never' ?></dd>
|
|
</dl>
|
|
<div class="form-actions" style="margin-top:1rem">
|
|
<?php if ($canManage): ?>
|
|
<?php if ($server['agent_installed'] ?? false): ?>
|
|
<button onclick="showAgentModal('uninstall')" class="btn btn-danger">
|
|
<i class="fas fa-minus-circle"></i> Uninstall Agent
|
|
</button>
|
|
<?php else: ?>
|
|
<button onclick="showAgentModal('install')" class="btn btn-primary">
|
|
<i class="fas fa-plus-circle"></i> Install Agent
|
|
</button>
|
|
<?php endif; ?>
|
|
<button onclick="regenerateAgentKey()" class="btn btn-secondary" title="Regenerate agent key">
|
|
<i class="fas fa-key"></i> Regenerate Key
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php else: ?>
|
|
<p class="text-muted">No agent key configured.</p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (!empty($permissions)): ?>
|
|
<div class="dashboard-card">
|
|
<div class="card-header">
|
|
<h2><i class="fas fa-shield-alt"></i> Required Permissions</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th>Type</th>
|
|
<th>Value</th>
|
|
<th>Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($permissions as $perm): ?>
|
|
<tr>
|
|
<td><span class="badge badge-info"><?= htmlspecialchars($perm['permission_type'] ?? '') ?></span></td>
|
|
<td><code><?= htmlspecialchars($perm['permission_value'] ?? '') ?></code></td>
|
|
<td><?= htmlspecialchars($perm['description'] ?? '') ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</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>
|
|
<?php if ($canManage): ?>
|
|
<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>
|
|
<a href="/servers/<?= $server['id'] ?>/services" class="action-card">
|
|
<i class="fas fa-cogs"></i>
|
|
<span>Services</span>
|
|
</a>
|
|
<button class="action-card" onclick="restartService()">
|
|
<i class="fas fa-sync-alt"></i>
|
|
<span>Restart Service</span>
|
|
</button>
|
|
<?php if ($server_role === 'owner'): ?>
|
|
<button class="action-card" onclick="showThresholdModal()">
|
|
<i class="fas fa-bell"></i>
|
|
<span>Notification Thresholds</span>
|
|
</button>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="agentConfirmModal" class="modal">
|
|
<div class="modal-dialog">
|
|
<div class="modal-header">
|
|
<h3 id="agentModalTitle">Install Monitoring Agent</h3>
|
|
<button type="button" class="modal-close" onclick="closeAgentModal()">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p id="agentModalMessage">
|
|
This will install the monitoring agent on <strong><?= htmlspecialchars($server['name'] ?? '') ?></strong> via SSH.
|
|
</p>
|
|
<p class="text-muted">
|
|
The agent will run as a systemd service, collecting and sending metrics every 60 seconds.
|
|
It will automatically restart if the server reboots.
|
|
</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="agentModalConfirmBtn" class="btn btn-primary" onclick="confirmAgentAction()">Install</button>
|
|
<button class="btn btn-secondary" onclick="closeAgentModal()">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="thresholdModal" class="modal">
|
|
<div class="modal-dialog" style="max-width:560px">
|
|
<div class="modal-header">
|
|
<h3><i class="fas fa-bell"></i> Notification Thresholds</h3>
|
|
<button type="button" class="modal-close" onclick="closeThresholdModal()">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="text-muted" style="margin-bottom:24px;font-size:0.9em;line-height:1.5">
|
|
When the agent reports values above these thresholds, all users with access to this server
|
|
receive a notification and push alert. Only the server owner can modify them.
|
|
</p>
|
|
<form id="thresholdForm" onsubmit="return false">
|
|
<?php $resources = [
|
|
'cpu' => ['label' => 'CPU', 'icon' => 'fas fa-microchip', 'color' => 'var(--info)'],
|
|
'ram' => ['label' => 'RAM', 'icon' => 'fas fa-memory', 'color' => 'var(--purple)'],
|
|
'disk' => ['label' => 'Disk', 'icon' => 'fas fa-hdd', 'color' => 'var(--warning)'],
|
|
]; ?>
|
|
<?php foreach ($resources as $key => $res): ?>
|
|
<div style="background:var(--card-bg);border:1px solid var(--border);border-radius:10px;padding:16px 20px;margin-bottom:12px">
|
|
<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">
|
|
<i class="<?= $res['icon'] ?>" style="color:<?= $res['color'] ?>;font-size:1.2em;width:20px;text-align:center"></i>
|
|
<strong style="font-size:1em"><?= $res['label'] ?></strong>
|
|
</div>
|
|
<div style="display:flex;gap:16px;align-items:center">
|
|
<div style="flex:1">
|
|
<label style="display:flex;align-items:center;gap:6px;font-size:0.82em;color:var(--warning);margin-bottom:4px;font-weight:600">
|
|
<i class="fas fa-exclamation-triangle" style="font-size:0.85em"></i> Warning
|
|
</label>
|
|
<div style="position:relative">
|
|
<input type="number" name="<?= $key ?>_warning"
|
|
value="<?= htmlspecialchars((string)($server["threshold_{$key}_warning"] ?? 80)) ?>"
|
|
min="0" max="100" step="0.01"
|
|
style="width:100%;padding:8px 12px 8px 32px;border:1px solid var(--border);border-radius:6px;background:var(--bg-dark);color:var(--text);font-size:0.95em;box-sizing:border-box">
|
|
<span style="position:absolute;right:10px;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:0.85em">%</span>
|
|
</div>
|
|
</div>
|
|
<div style="flex:1">
|
|
<label style="display:flex;align-items:center;gap:6px;font-size:0.82em;color:var(--danger);margin-bottom:4px;font-weight:600">
|
|
<i class="fas fa-bolt" style="font-size:0.85em"></i> Critical
|
|
</label>
|
|
<div style="position:relative">
|
|
<input type="number" name="<?= $key ?>_critical"
|
|
value="<?= htmlspecialchars((string)($server["threshold_{$key}_critical"] ?? 95)) ?>"
|
|
min="0" max="100" step="0.01"
|
|
style="width:100%;padding:8px 12px 8px 32px;border:1px solid var(--border);border-radius:6px;background:var(--bg-dark);color:var(--text);font-size:0.95em;box-sizing:border-box">
|
|
<span style="position:absolute;right:10px;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:0.85em">%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-primary" onclick="saveThresholds()"><i class="fas fa-save"></i> Save Thresholds</button>
|
|
<button class="btn btn-secondary" onclick="closeThresholdModal()">Cancel</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);
|
|
}
|
|
|
|
let pendingAgentAction = null;
|
|
|
|
function showAgentModal(action) {
|
|
pendingAgentAction = action;
|
|
const modal = document.getElementById('agentConfirmModal');
|
|
const title = document.getElementById('agentModalTitle');
|
|
const msg = document.getElementById('agentModalMessage');
|
|
const btn = document.getElementById('agentModalConfirmBtn');
|
|
|
|
if (action === 'install') {
|
|
title.textContent = 'Install Monitoring Agent';
|
|
msg.innerHTML = 'This will install the monitoring agent on <strong><?= htmlspecialchars($server['name'] ?? '') ?></strong> via SSH.';
|
|
btn.textContent = 'Install';
|
|
btn.className = 'btn btn-primary';
|
|
} else {
|
|
title.textContent = 'Uninstall Monitoring Agent';
|
|
msg.innerHTML = 'This will uninstall the monitoring agent from <strong><?= htmlspecialchars($server['name'] ?? '') ?></strong> via SSH. The service will be stopped and all agent files removed.';
|
|
btn.textContent = 'Uninstall';
|
|
btn.className = 'btn btn-danger';
|
|
}
|
|
|
|
modal.style.display = 'flex';
|
|
modal.onclick = function(e) {
|
|
if (e.target === modal) closeAgentModal();
|
|
};
|
|
}
|
|
|
|
function closeAgentModal() {
|
|
document.getElementById('agentConfirmModal').style.display = 'none';
|
|
pendingAgentAction = null;
|
|
}
|
|
|
|
function confirmAgentAction() {
|
|
if (!pendingAgentAction) return;
|
|
|
|
const action = pendingAgentAction;
|
|
const btn = document.getElementById('agentModalConfirmBtn');
|
|
btn.disabled = true;
|
|
btn.textContent = action === 'install' ? 'Installing...' : 'Uninstalling...';
|
|
|
|
closeAgentModal();
|
|
|
|
fetch('/servers/<?= $server['id'] ?>/agent/' + 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 => {
|
|
if (data.success) {
|
|
showToast('success', action === 'install' ? 'Agent installed successfully.' : 'Agent uninstalled successfully.');
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
let msg = data.error || (action === 'install' ? 'Installation failed.' : 'Uninstallation failed.');
|
|
if (data.output) msg += ' Output: ' + data.output.replace(/\n/g, ' | ');
|
|
showToast('error', msg);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
showToast('error', 'Request failed: ' + err.message);
|
|
})
|
|
.finally(() => {
|
|
btn.disabled = false;
|
|
btn.textContent = action === 'install' ? 'Install' : 'Uninstall';
|
|
});
|
|
}
|
|
|
|
function copyAgentKey() {
|
|
const key = document.getElementById('agentKeyFull').value;
|
|
navigator.clipboard.writeText(key).then(() => {
|
|
showToast('success', 'Agent key copied to clipboard.');
|
|
}).catch(() => {
|
|
showToast('error', 'Failed to copy agent key.');
|
|
});
|
|
}
|
|
|
|
function regenerateAgentKey() {
|
|
if (!confirm('Regenerate agent key? The current key will stop working immediately.')) return;
|
|
|
|
fetch('/servers/<?= $server['id'] ?>/agent/regenerate-key', {
|
|
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 => {
|
|
if (data.success) {
|
|
showToast('success', 'Agent key regenerated.');
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
showToast('error', data.error || 'Failed to regenerate key.');
|
|
}
|
|
})
|
|
.catch(err => showToast('error', 'Request failed: ' + err.message));
|
|
}
|
|
|
|
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);
|
|
|
|
function showThresholdModal() {
|
|
document.getElementById('thresholdModal').style.display = 'flex';
|
|
document.getElementById('thresholdModal').onclick = function(e) {
|
|
if (e.target === this) closeThresholdModal();
|
|
};
|
|
}
|
|
|
|
function closeThresholdModal() {
|
|
document.getElementById('thresholdModal').style.display = 'none';
|
|
}
|
|
|
|
function saveThresholds() {
|
|
const form = document.getElementById('thresholdForm');
|
|
const data = new URLSearchParams(new FormData(form));
|
|
data.append('_csrf_token', getCsrfToken());
|
|
|
|
document.querySelector('#thresholdModal .btn-primary').disabled = true;
|
|
|
|
fetch('/servers/<?= $server['id'] ?>/thresholds', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'X-CSRF-Token': getCsrfToken(),
|
|
},
|
|
body: data.toString(),
|
|
})
|
|
.then(r => r.json())
|
|
.then(d => {
|
|
showToast(d.success ? 'success' : 'error', d.message ?? d.error);
|
|
if (d.success) closeThresholdModal();
|
|
})
|
|
.catch(e => showToast('error', 'Request failed: ' + e.message))
|
|
.finally(() => {
|
|
document.querySelector('#thresholdModal .btn-primary').disabled = false;
|
|
});
|
|
}
|
|
</script>
|