From bfa102cf97cb5136dfc375b56d9b37cc5a858c9c Mon Sep 17 00:00:00 2001 From: Agent Date: Thu, 11 Jun 2026 18:28:22 -0400 Subject: [PATCH] feat: push-based monitoring with remote agent - Add database migration 006 for agent_key, agent_installed, agent_install_path, agent_last_seen_at columns - Add Server model methods: findByAgentKey, generateAgentKey, regenerateAgentKey, updateAgentLastSeen, setAgentInstalled - Auto-generate agent_key on server creation - Create AgentService for SSH-based install/uninstall of remote agent - Create bin/agent.sh - bash agent script for remote servers - Create install/agent-install.sh and install/agent-uninstall.sh - Create install/servermanager-agent.service systemd unit - Add POST /api/metrics/push endpoint (auth via agent_key) - Add web routes for agent install/uninstall/regenerate-key - Add agent status section to server detail view with install/uninstall modals - Make MonitoringService::saveMetrics() public for reuse --- bin/agent.sh | 83 +++++++++++ database/migrations/006_agent_tokens.sql | 9 ++ install/agent-install.sh | 136 ++++++++++++++++++ install/agent-uninstall.sh | 22 +++ install/servermanager-agent.service | 21 +++ routes/web.php | 5 + src/Controllers/ApiController.php | 39 +++++ src/Controllers/ServerController.php | 34 +++++ src/Models/Server.php | 47 ++++++ src/Services/AgentService.php | 159 ++++++++++++++++++++ src/Services/MonitoringService.php | 2 +- views/servers/show.php | 175 +++++++++++++++++++++++ 12 files changed, 731 insertions(+), 1 deletion(-) create mode 100644 bin/agent.sh create mode 100644 database/migrations/006_agent_tokens.sql create mode 100644 install/agent-install.sh create mode 100644 install/agent-uninstall.sh create mode 100644 install/servermanager-agent.service create mode 100644 src/Services/AgentService.php diff --git a/bin/agent.sh b/bin/agent.sh new file mode 100644 index 0000000..8638b57 --- /dev/null +++ b/bin/agent.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# ServerManager Monitoring Agent +# This script runs as a systemd service on monitored servers. +set -e + +CONFIG_FILE="/etc/servermanager/agent.conf" + +load_config() { + if [ -f "$CONFIG_FILE" ]; then + source "$CONFIG_FILE" + fi + SERVER_URL="${SERVER_URL:-}" + AGENT_KEY="${AGENT_KEY:-}" + INTERVAL="${INTERVAL:-60}" +} + +collect_metrics() { + CPU=$(top -bn1 2>/dev/null | grep 'Cpu(s)' | awk '{print $2}' | cut -d'%' -f1) + [ -z "$CPU" ] && CPU=0 + + RAM=$(free 2>/dev/null | grep Mem | awk '{printf "%.1f", $3/$2 * 100}') + [ -z "$RAM" ] && RAM=0 + + DISK=$(df / 2>/dev/null | tail -1 | awk '{print $5}' | sed 's/%//') + [ -z "$DISK" ] && DISK=0 + + LOAD=$(cat /proc/loadavg 2>/dev/null | awk '{print $1}') + [ -z "$LOAD" ] && LOAD=0 + + UPTIME=$(uptime -p 2>/dev/null | sed 's/^up //') + [ -z "$UPTIME" ] && UPTIME="" +} + +push_metrics() { + local payload + payload=$(cat </dev/null || echo "000" +} + +main() { + load_config + + if [ -z "$SERVER_URL" ] || [ -z "$AGENT_KEY" ]; then + echo "ERROR: SERVER_URL and AGENT_KEY must be set in $CONFIG_FILE" + exit 1 + fi + + echo "ServerManager Agent started" + echo "Server: $SERVER_URL" + echo "Interval: ${INTERVAL}s" + + sleep 5 + + while true; do + collect_metrics + http_code=$(push_metrics) + + if [ "$http_code" = "200" ]; then + echo "[$(date)] Metrics pushed successfully" + else + echo "[$(date)] Failed to push metrics (HTTP $http_code)" + fi + + sleep "$INTERVAL" + done +} + +main diff --git a/database/migrations/006_agent_tokens.sql b/database/migrations/006_agent_tokens.sql new file mode 100644 index 0000000..8fd5eb1 --- /dev/null +++ b/database/migrations/006_agent_tokens.sql @@ -0,0 +1,9 @@ +ALTER TABLE servers + ADD COLUMN agent_key VARCHAR(64) DEFAULT NULL AFTER ssh_key, + ADD COLUMN agent_installed TINYINT(1) NOT NULL DEFAULT 0 AFTER agent_key, + ADD COLUMN agent_install_path VARCHAR(255) DEFAULT NULL AFTER agent_installed, + ADD COLUMN agent_last_seen_at DATETIME DEFAULT NULL AFTER agent_install_path; + +CREATE UNIQUE INDEX idx_servers_agent_key ON servers(agent_key); + +UPDATE servers SET agent_key = LOWER(HEX(RANDOM_BYTES(32))) WHERE agent_key IS NULL; diff --git a/install/agent-install.sh b/install/agent-install.sh new file mode 100644 index 0000000..eeb3348 --- /dev/null +++ b/install/agent-install.sh @@ -0,0 +1,136 @@ +#!/bin/bash +# ServerManager Agent Installer +# Usage: bash agent-install.sh [INTERVAL] +set -e + +SERVER_URL="${1:-}" +AGENT_KEY="${2:-}" +INTERVAL="${3:-60}" + +if [ -z "$SERVER_URL" ] || [ -z "$AGENT_KEY" ]; then + echo "Usage: bash agent-install.sh [INTERVAL]" + exit 1 +fi + +AGENT_BIN="/usr/local/bin/servermanager-agent" +CONFIG_DIR="/etc/servermanager" +CONFIG_FILE="$CONFIG_DIR/agent.conf" +SERVICE_FILE="/etc/systemd/system/servermanager-agent.service" + +echo "[1/5] Creating config directory..." +mkdir -p "$CONFIG_DIR" + +echo "[2/5] Installing agent binary..." +cat > "$AGENT_BIN" << 'AGENTSCRIPT' +#!/bin/bash +set -e +CONFIG_FILE="/etc/servermanager/agent.conf" + +load_config() { + if [ -f "$CONFIG_FILE" ]; then + source "$CONFIG_FILE" + fi + SERVER_URL="${SERVER_URL:-}" + AGENT_KEY="${AGENT_KEY:-}" + INTERVAL="${INTERVAL:-60}" +} + +collect_metrics() { + CPU=$(top -bn1 2>/dev/null | grep 'Cpu(s)' | awk '{print $2}' | cut -d'%' -f1) + [ -z "$CPU" ] && CPU=0 + RAM=$(free 2>/dev/null | grep Mem | awk '{printf "%.1f", $3/$2 * 100}') + [ -z "$RAM" ] && RAM=0 + DISK=$(df / 2>/dev/null | tail -1 | awk '{print $5}' | sed 's/%//') + [ -z "$DISK" ] && DISK=0 + LOAD=$(cat /proc/loadavg 2>/dev/null | awk '{print $1}') + [ -z "$LOAD" ] && LOAD=0 + UPTIME=$(uptime -p 2>/dev/null | sed 's/^up //') + [ -z "$UPTIME" ] && UPTIME="" +} + +push_metrics() { + local payload + payload=$(cat </dev/null || echo "000" +} + +main() { + load_config + if [ -z "$SERVER_URL" ] || [ -z "$AGENT_KEY" ]; then + echo "ERROR: SERVER_URL and AGENT_KEY must be set in $CONFIG_FILE" + exit 1 + fi + echo "ServerManager Agent started (interval: ${INTERVAL}s)" + sleep 5 + while true; do + collect_metrics + http_code=$(push_metrics) + if [ "$http_code" = "200" ]; then + echo "[$(date)] Metrics pushed successfully" + else + echo "[$(date)] Failed to push metrics (HTTP $http_code)" + fi + sleep "$INTERVAL" + done +} +main +AGENTSCRIPT +chmod +x "$AGENT_BIN" + +echo "[3/5] Writing config..." +cat > "$CONFIG_FILE" << EOF +SERVER_URL="$SERVER_URL" +AGENT_KEY="$AGENT_KEY" +INTERVAL=$INTERVAL +EOF +chmod 600 "$CONFIG_FILE" + +echo "[4/5] Installing systemd service..." +cat > "$SERVICE_FILE" << 'UNIT' +[Unit] +Description=ServerManager Monitoring Agent +Documentation=https://github.com/servermanager +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/servermanager-agent +Restart=always +RestartSec=10 +StartLimitIntervalSec=60 +StartLimitBurst=5 +NoNewPrivileges=yes +ProtectSystem=full +ProtectHome=yes +PrivateDevices=yes +PrivateTmp=yes + +[Install] +WantedBy=multi-user.target +UNIT + +echo "[5/5] Enabling and starting service..." +systemctl daemon-reload +systemctl enable servermanager-agent +systemctl restart servermanager-agent + +echo "" +echo "ServerManager Agent installed successfully!" +echo " Binary: $AGENT_BIN" +echo " Config: $CONFIG_FILE" +echo " Service: $SERVICE_FILE" diff --git a/install/agent-uninstall.sh b/install/agent-uninstall.sh new file mode 100644 index 0000000..c4ce3ee --- /dev/null +++ b/install/agent-uninstall.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ServerManager Agent Uninstaller +set -e + +AGENT_BIN="/usr/local/bin/servermanager-agent" +CONFIG_DIR="/etc/servermanager" +SERVICE_FILE="/etc/systemd/system/servermanager-agent.service" + +echo "[1/3] Stopping and disabling service..." +systemctl stop servermanager-agent 2>/dev/null || true +systemctl disable servermanager-agent 2>/dev/null || true +systemctl daemon-reload + +echo "[2/3] Removing files..." +rm -f "$AGENT_BIN" +rm -rf "$CONFIG_DIR" +rm -f "$SERVICE_FILE" + +echo "[3/3] Reloading systemd..." +systemctl daemon-reload + +echo "ServerManager Agent uninstalled successfully." diff --git a/install/servermanager-agent.service b/install/servermanager-agent.service new file mode 100644 index 0000000..3c24384 --- /dev/null +++ b/install/servermanager-agent.service @@ -0,0 +1,21 @@ +[Unit] +Description=ServerManager Monitoring Agent +Documentation=https://github.com/servermanager +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/servermanager-agent +Restart=always +RestartSec=10 +StartLimitIntervalSec=60 +StartLimitBurst=5 +NoNewPrivileges=yes +ProtectSystem=full +ProtectHome=yes +PrivateDevices=yes +PrivateTmp=yes + +[Install] +WantedBy=multi-user.target diff --git a/routes/web.php b/routes/web.php index 9088f07..3d1df32 100755 --- a/routes/web.php +++ b/routes/web.php @@ -122,3 +122,8 @@ $router->get('/api/users', [ApiController::class, 'users']); $router->get('/api/profile', [ApiController::class, 'profile']); $router->put('/api/profile', [ApiController::class, 'updateProfile']); $router->get('/api/refresh-metrics', [ApiController::class, 'refreshAllMetrics']); +$router->post('/api/metrics/push', [ApiController::class, 'pushMetrics']); + +$router->post('/servers/:id/agent/install', [ServerController::class, 'agentInstall'], [AuthMiddleware::class, CSRFMiddleware::class]); +$router->post('/servers/:id/agent/uninstall', [ServerController::class, 'agentUninstall'], [AuthMiddleware::class, CSRFMiddleware::class]); +$router->post('/servers/:id/agent/regenerate-key', [ServerController::class, 'agentRegenerateKey'], [AuthMiddleware::class, CSRFMiddleware::class]); diff --git a/src/Controllers/ApiController.php b/src/Controllers/ApiController.php index 189b4e8..0cb507d 100755 --- a/src/Controllers/ApiController.php +++ b/src/Controllers/ApiController.php @@ -114,6 +114,8 @@ class ApiController $monitoringService = new MonitoringService(); $metrics = $monitoringService->getLatestMetrics($id); + unset($server['agent_key'], $server['ssh_password'], $server['ssh_key']); + $this->view->json([ 'success' => true, 'data' => [ @@ -296,6 +298,43 @@ class ApiController ]); } + public function pushMetrics(): void + { + $input = json_decode(file_get_contents('php://input'), true); + + if (!$input || empty($input['agent_key'])) { + $this->view->json(['error' => 'agent_key is required'], 401); + } + + $serverModel = new Server(); + $server = $serverModel->findByAgentKey($input['agent_key']); + + if (!$server || !$server['is_active']) { + $this->view->json(['error' => 'Invalid agent key'], 401); + } + + $cpu = isset($input['cpu']) ? max(0, min(100, (float) $input['cpu'])) : 0; + $ram = isset($input['ram']) ? max(0, min(100, (float) $input['ram'])) : 0; + $disk = isset($input['disk']) ? max(0, min(100, (float) $input['disk'])) : 0; + $load = (float) ($input['load'] ?? 0); + $uptime = (string) ($input['uptime'] ?? ''); + + $monitoring = new MonitoringService(); + $monitoring->saveMetrics([ + 'server_id' => (int) $server['id'], + 'status' => 'online', + 'cpu_usage' => $cpu, + 'ram_usage' => $ram, + 'disk_usage' => $disk, + 'load_average' => $load, + 'uptime' => $uptime, + ]); + + $serverModel->updateAgentLastSeen((int) $server['id']); + + $this->view->json(['success' => true]); + } + public function refreshAllMetrics(): void { $user = $this->authenticateRequest(); diff --git a/src/Controllers/ServerController.php b/src/Controllers/ServerController.php index e29e9bc..4bf3970 100755 --- a/src/Controllers/ServerController.php +++ b/src/Controllers/ServerController.php @@ -255,6 +255,40 @@ class ServerController ]); } + public function agentInstall(int $id): void + { + $server = $this->requireServerAccess($id, 'manager'); + + $agentService = new \ServerManager\Services\AgentService(); + $result = $agentService->install($id); + + $this->view->json($result); + } + + public function agentUninstall(int $id): void + { + $server = $this->requireServerAccess($id, 'manager'); + + $agentService = new \ServerManager\Services\AgentService(); + $result = $agentService->uninstall($id); + + $this->view->json($result); + } + + public function agentRegenerateKey(int $id): void + { + $this->requireServerAccess($id, 'manager'); + + $newKey = $this->serverModel->regenerateAgentKey($id); + + $this->auditService->log('agent_key_regenerated', 'server', $id); + + $this->view->json([ + 'success' => true, + 'agent_key' => $newKey, + ]); + } + public function testConnection(int $id): void { $server = $this->requireServerAccess($id, 'viewer'); diff --git a/src/Models/Server.php b/src/Models/Server.php index 66b21fb..f29c8a5 100755 --- a/src/Models/Server.php +++ b/src/Models/Server.php @@ -122,6 +122,10 @@ class Server $data['ssh_key'] = null; } + if (empty($data['agent_key'])) { + $data['agent_key'] = $this->generateAgentKey(); + } + $data['created_at'] = date('Y-m-d H:i:s'); $data['updated_at'] = date('Y-m-d H:i:s'); @@ -405,4 +409,47 @@ class Server [$userId] ); } + + public function findByAgentKey(string $key): ?array + { + $server = $this->db->fetch( + 'SELECT * FROM servers WHERE agent_key = ? AND status = \'active\'', + [$key] + ); + if ($server) { + $server = $this->encryption->decryptArray($server, $this->encryptedFields); + } + return $server; + } + + public function generateAgentKey(): string + { + return bin2hex(random_bytes(32)); + } + + public function regenerateAgentKey(int $id): string + { + $key = $this->generateAgentKey(); + $this->db->update('servers', [ + 'agent_key' => $key, + 'updated_at' => date('Y-m-d H:i:s'), + ], 'id = ?', [$id]); + return $key; + } + + public function updateAgentLastSeen(int $id): bool + { + return $this->db->update('servers', [ + 'agent_last_seen_at' => date('Y-m-d H:i:s'), + ], 'id = ?', [$id]) > 0; + } + + public function setAgentInstalled(int $id, bool $installed, ?string $path = null): bool + { + return $this->db->update('servers', [ + 'agent_installed' => $installed ? 1 : 0, + 'agent_install_path' => $path, + 'updated_at' => date('Y-m-d H:i:s'), + ], 'id = ?', [$id]) > 0; + } } diff --git a/src/Services/AgentService.php b/src/Services/AgentService.php new file mode 100644 index 0000000..400efbd --- /dev/null +++ b/src/Services/AgentService.php @@ -0,0 +1,159 @@ +db = Database::getInstance(); + $this->logger = Logger::getInstance(); + $this->basePath = dirname(__DIR__, 2); + } + + public function install(int $serverId): array + { + $server = $this->db->fetch( + 'SELECT * FROM servers WHERE id = ? AND status = \'active\' AND is_active = 1', + [$serverId] + ); + + if (!$server) { + return ['success' => false, 'error' => 'Server not found or inactive']; + } + + $server = App::getEncryption()->decryptArray($server, ['ssh_password', 'ssh_key']); + + $agentKey = $server['agent_key']; + $serverUrl = rtrim((App::getConfig()['app']['url'] ?? ''), '/'); + + if (empty($serverUrl)) { + $serverUrl = ($_SERVER['HTTPS'] ?? '' === 'on' ? 'https://' : 'http://') . ($_SERVER['HTTP_HOST'] ?? 'localhost'); + } + + $scriptPath = $this->basePath . '/install/agent-install.sh'; + + if (!file_exists($scriptPath)) { + return ['success' => false, 'error' => 'Agent install script not found']; + } + + $script = file_get_contents($scriptPath); + + $remoteCommand = sprintf( + 'echo %s | base64 -d | bash -s -- %s %s 2>&1', + escapeshellarg(base64_encode($script)), + escapeshellarg($serverUrl), + escapeshellarg($agentKey) + ); + + try { + $ssh = new SSHService(); + if (!$ssh->connect($server, true)) { + return ['success' => false, 'error' => 'SSH connection failed']; + } + + $result = $ssh->exec($remoteCommand, 120); + $ssh->disconnect(); + + $output = trim($result['output']); + $exitStatus = $result['exit_status'] ?? -1; + + if ($exitStatus !== 0) { + return [ + 'success' => false, + 'error' => 'Agent installation failed on remote server', + 'output' => $output, + ]; + } + + $path = '/usr/local/bin/servermanager-agent'; + + $serverModel = new \ServerManager\Models\Server(); + $serverModel->setAgentInstalled($serverId, true, $path); + + $auditService = new AuditService(); + $auditService->log('agent_installed', 'server', $serverId, [ + 'name' => $server['name'], + 'path' => $path, + ]); + + return ['success' => true, 'path' => $path, 'output' => $output]; + } catch (\Throwable $e) { + $this->logger->error("Agent install failed for server {$serverId}: " . $e->getMessage()); + return ['success' => false, 'error' => $e->getMessage()]; + } + } + + public function uninstall(int $serverId): array + { + $server = $this->db->fetch( + 'SELECT * FROM servers WHERE id = ? AND status = \'active\' AND is_active = 1', + [$serverId] + ); + + if (!$server) { + return ['success' => false, 'error' => 'Server not found or inactive']; + } + + $server = App::getEncryption()->decryptArray($server, ['ssh_password', 'ssh_key']); + + $scriptPath = $this->basePath . '/install/agent-uninstall.sh'; + + if (!file_exists($scriptPath)) { + return ['success' => false, 'error' => 'Agent uninstall script not found']; + } + + $script = file_get_contents($scriptPath); + + $remoteCommand = sprintf( + 'echo %s | base64 -d | bash 2>&1', + escapeshellarg(base64_encode($script)) + ); + + try { + $ssh = new SSHService(); + if (!$ssh->connect($server, true)) { + return ['success' => false, 'error' => 'SSH connection failed']; + } + + $result = $ssh->exec($remoteCommand, 60); + $ssh->disconnect(); + + $output = trim($result['output']); + $exitStatus = $result['exit_status'] ?? -1; + + $serverModel = new \ServerManager\Models\Server(); + $serverModel->setAgentInstalled($serverId, false, null); + + $auditService = new AuditService(); + $auditService->log('agent_uninstalled', 'server', $serverId, [ + 'name' => $server['name'], + ]); + + if ($exitStatus !== 0) { + return [ + 'success' => false, + 'error' => 'Agent uninstallation may have failed on remote server', + 'output' => $output, + ]; + } + + return ['success' => true, 'output' => $output]; + } catch (\Throwable $e) { + $this->logger->error("Agent uninstall failed for server {$serverId}: " . $e->getMessage()); + $serverModel = new \ServerManager\Models\Server(); + $serverModel->setAgentInstalled($serverId, false, null); + return ['success' => false, 'error' => $e->getMessage()]; + } + } +} diff --git a/src/Services/MonitoringService.php b/src/Services/MonitoringService.php index 8ccb0fa..83e08cc 100755 --- a/src/Services/MonitoringService.php +++ b/src/Services/MonitoringService.php @@ -111,7 +111,7 @@ class MonitoringService return trim(str_replace('up ', '', $result['output'])); } - private function saveMetrics(array $metrics): void + public function saveMetrics(array $metrics): void { $now = date('Y-m-d H:i:s'); diff --git a/views/servers/show.php b/views/servers/show.php index efcf1ef..d03fd1a 100755 --- a/views/servers/show.php +++ b/views/servers/show.php @@ -139,6 +139,58 @@ $canManage = $server_role === 'owner' || $server_role === 'manager'; + +
+
+

Monitoring Agent

+
+
+ +
+
Agent Key
+
+ ... + + +
+
Status
+
+ + Installed + + Not installed + +
+ +
Install Path
+
+ +
Last Seen
+
+
+
+ + + + + + + + +
+ +

No agent key configured.

+ +
+
@@ -195,6 +247,28 @@ $canManage = $server_role === 'owner' || $server_role === 'manager';
+ + @@ -315,6 +389,107 @@ 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 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 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//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 { + showToast('error', data.error || (action === 'install' ? 'Installation failed.' : 'Uninstallation failed.')); + if (data.output) console.log('Output:', data.output); + } + }) + .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//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 || ''; } -- 2.43.0