From 4ce6b27f96fe29b168e4c90a33266f463d8fd289 Mon Sep 17 00:00:00 2001 From: Agent Date: Sun, 14 Jun 2026 09:02:13 -0400 Subject: [PATCH 1/6] fix: use serverId variable and add Accept: application/json to server-show.js fetch calls --- public/assets/js/server-show.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/public/assets/js/server-show.js b/public/assets/js/server-show.js index 7ba0230..a667c15 100644 --- a/public/assets/js/server-show.js +++ b/public/assets/js/server-show.js @@ -152,9 +152,10 @@ function confirmAgentAction() { closeAgentModal(); - fetch('/servers/serverId/agent/' + action, { + fetch('/servers/' + serverId + '/agent/' + action, { method: 'POST', headers: { + 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-Token': getCsrfToken(), }, @@ -192,9 +193,10 @@ function copyAgentKey() { function regenerateAgentKey() { if (!confirm('Regenerate agent key? The current key will stop working immediately.')) return; - fetch('/servers/serverId/agent/regenerate-key', { + fetch('/servers/' + serverId + '/agent/regenerate-key', { method: 'POST', headers: { + 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-Token': getCsrfToken(), }, @@ -221,9 +223,10 @@ function serverAction(action) { if (!confirm('Are you sure you want to ' + action + ' this server?')) return; } - fetch('/servers/serverId/' + action, { + fetch('/servers/' + serverId + '/' + action, { method: 'POST', headers: { + 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-Token': getCsrfToken(), }, @@ -240,9 +243,10 @@ function restartService() { const service = prompt('Enter service name to restart (e.g., nginx, mysql, apache2):'); if (!service) return; - fetch('/servers/serverId/restart-service', { + fetch('/servers/' + serverId + '/restart-service', { method: 'POST', headers: { + 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-Token': getCsrfToken(), }, @@ -256,7 +260,9 @@ function restartService() { } function refreshMetrics() { - fetch('/servers/serverId/metrics') + fetch('/servers/' + serverId + '/metrics', { + headers: { 'Accept': 'application/json' }, + }) .then(r => r.json()) .then(data => { if (data.success && data.data) { @@ -303,9 +309,10 @@ function saveThresholds() { document.querySelector('#thresholdModal .btn-primary').disabled = true; - fetch('/servers/serverId/thresholds', { + fetch('/servers/' + serverId + '/thresholds', { method: 'POST', headers: { + 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-Token': getCsrfToken(), }, -- 2.43.0 From 12ad0e0510a716e46b59889c5d24b03d211c0f64 Mon Sep 17 00:00:00 2001 From: Agent Date: Sun, 14 Jun 2026 09:03:53 -0400 Subject: [PATCH 2/6] fix: cache CSRF token in View to prevent mismatch on multiple renders --- src/Core/View.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Core/View.php b/src/Core/View.php index 1736420..9b9d8a6 100755 --- a/src/Core/View.php +++ b/src/Core/View.php @@ -10,6 +10,7 @@ class View private string $layout = 'main'; private array $data = []; private Security $security; + private ?string $csrfTokenCache = null; public function __construct(Security $security) { @@ -80,7 +81,10 @@ class View public function csrfToken(): string { - return $this->security->generateCSRFToken(); + if ($this->csrfTokenCache === null) { + $this->csrfTokenCache = $this->security->generateCSRFToken(); + } + return $this->csrfTokenCache; } public function csrfField(): string -- 2.43.0 From 5c802f9c1efc35c2da0b7b7ef0fb164b81b4fe01 Mon Sep 17 00:00:00 2001 From: Agent Date: Sun, 14 Jun 2026 09:05:13 -0400 Subject: [PATCH 3/6] fix: add missing closing brace to TerminalController::execute method --- src/Controllers/TerminalController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Controllers/TerminalController.php b/src/Controllers/TerminalController.php index 7565150..b20424f 100755 --- a/src/Controllers/TerminalController.php +++ b/src/Controllers/TerminalController.php @@ -122,6 +122,7 @@ class TerminalController 'message' => 'Failed to execute command.', ]); } + } public function history(int $id): void { -- 2.43.0 From 7233c224ce3e75b754c7f9ee6678feefe5198152 Mon Sep 17 00:00:00 2001 From: Agent Date: Sun, 14 Jun 2026 09:07:46 -0400 Subject: [PATCH 4/6] fix: set status='active' on command_history insert so history displays --- src/Models/CommandHistory.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Models/CommandHistory.php b/src/Models/CommandHistory.php index 2f1c610..c2af2ad 100755 --- a/src/Models/CommandHistory.php +++ b/src/Models/CommandHistory.php @@ -23,6 +23,7 @@ class CommandHistory 'command' => $command, 'output' => mb_substr($output ?? '', 0, 65535), 'exit_status' => $exitStatus, + 'status' => 'active', 'executed_at' => date('Y-m-d H:i:s'), ]); } -- 2.43.0 From 49d724cf9c142f8cf35a69e8d44481a173745838 Mon Sep 17 00:00:00 2001 From: Agent Date: Sun, 14 Jun 2026 09:09:40 -0400 Subject: [PATCH 5/6] fix: add Accept: application/json to terminal execute fetch --- public/assets/js/terminal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/assets/js/terminal.js b/public/assets/js/terminal.js index bbcd415..8f3bfd2 100644 --- a/public/assets/js/terminal.js +++ b/public/assets/js/terminal.js @@ -27,6 +27,7 @@ function executeCommand() { fetch('/terminal/' + serverId + '/execute', { method: 'POST', headers: { + 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-Token': getCsrfToken(), }, -- 2.43.0 From b7bd4bfe92c36885faff4149dd1739c298b82bb8 Mon Sep 17 00:00:00 2001 From: Agent Date: Sun, 14 Jun 2026 09:12:28 -0400 Subject: [PATCH 6/6] fix: append command history row in real-time after execution --- public/assets/js/terminal.js | 26 ++++++++++++++++++++++++++ src/Controllers/TerminalController.php | 9 ++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/public/assets/js/terminal.js b/public/assets/js/terminal.js index 8f3bfd2..bafc71a 100644 --- a/public/assets/js/terminal.js +++ b/public/assets/js/terminal.js @@ -12,6 +12,12 @@ terminalInput.addEventListener('keydown', function(e) { } }); +function escapeHtml(str) { + const d = document.createElement('div'); + d.textContent = str; + return d.innerHTML; +} + function executeCommand() { const command = terminalInput.value.trim(); if (!command) return; @@ -46,6 +52,9 @@ function executeCommand() { if (data.stderr) { appendTerminalLine(data.stderr, 'error'); } + if (data.history) { + appendHistoryRow(data.history); + } updateStatus('connected', 'Connected'); } else { appendTerminalLine(data.message || 'Command failed', 'error'); @@ -84,6 +93,23 @@ function clearTerminal() { terminalOutput.innerHTML = ''; } +function appendHistoryRow(h) { + const tbody = document.querySelector('.card-body .table tbody'); + if (!tbody) return; + const emptyState = tbody.closest('.card-body')?.querySelector('.empty-state'); + if (emptyState) emptyState.remove(); + const row = document.createElement('tr'); + const time = h.executed_at ? new Date(h.executed_at.replace(' ', 'T')).toLocaleTimeString('en-US', { hour12: false }) : ''; + row.innerHTML = + '' + time + '' + + '' + escapeHtml(h.command) + '' + + '' + (h.exit_status === 0 ? 'OK' : 'ERR') + '' + + '' + escapeHtml(h.username || 'System') + ''; + tbody.prepend(row); + const container = tbody.closest('.table-responsive'); + if (container) container.scrollTop = 0; +} + function clearHistory() { if (!confirm('Clear all command history for this server?')) return; diff --git a/src/Controllers/TerminalController.php b/src/Controllers/TerminalController.php index b20424f..8e22e0b 100755 --- a/src/Controllers/TerminalController.php +++ b/src/Controllers/TerminalController.php @@ -100,7 +100,7 @@ class TerminalController $ssh->disconnect(); $commandHistory = new CommandHistory(); - $commandHistory->log( + $historyId = $commandHistory->log( $id, Session::get('user_id'), $command, @@ -115,6 +115,13 @@ class TerminalController 'output' => $result['output'], 'exit_status' => $result['exit_status'], 'stderr' => $result['stderr'], + 'history' => [ + 'id' => $historyId, + 'command' => $command, + 'exit_status' => $result['exit_status'] ?? 0, + 'executed_at' => date('Y-m-d H:i:s'), + 'username' => Session::get('username'), + ], ]); } catch (\Throwable $e) { $this->view->json([ -- 2.43.0