Merge pull request 'fix/policy-container-width' (#91) from fix/policy-container-width into master
Reviewed-on: #91
This commit was merged in pull request #91.
This commit is contained in:
@@ -152,9 +152,10 @@ function confirmAgentAction() {
|
|||||||
|
|
||||||
closeAgentModal();
|
closeAgentModal();
|
||||||
|
|
||||||
fetch('/servers/serverId/agent/' + action, {
|
fetch('/servers/' + serverId + '/agent/' + action, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-CSRF-Token': getCsrfToken(),
|
'X-CSRF-Token': getCsrfToken(),
|
||||||
},
|
},
|
||||||
@@ -192,9 +193,10 @@ function copyAgentKey() {
|
|||||||
function regenerateAgentKey() {
|
function regenerateAgentKey() {
|
||||||
if (!confirm('Regenerate agent key? The current key will stop working immediately.')) return;
|
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',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-CSRF-Token': getCsrfToken(),
|
'X-CSRF-Token': getCsrfToken(),
|
||||||
},
|
},
|
||||||
@@ -221,9 +223,10 @@ function serverAction(action) {
|
|||||||
if (!confirm('Are you sure you want to ' + action + ' this server?')) return;
|
if (!confirm('Are you sure you want to ' + action + ' this server?')) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch('/servers/serverId/' + action, {
|
fetch('/servers/' + serverId + '/' + action, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-CSRF-Token': getCsrfToken(),
|
'X-CSRF-Token': getCsrfToken(),
|
||||||
},
|
},
|
||||||
@@ -240,9 +243,10 @@ function restartService() {
|
|||||||
const service = prompt('Enter service name to restart (e.g., nginx, mysql, apache2):');
|
const service = prompt('Enter service name to restart (e.g., nginx, mysql, apache2):');
|
||||||
if (!service) return;
|
if (!service) return;
|
||||||
|
|
||||||
fetch('/servers/serverId/restart-service', {
|
fetch('/servers/' + serverId + '/restart-service', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-CSRF-Token': getCsrfToken(),
|
'X-CSRF-Token': getCsrfToken(),
|
||||||
},
|
},
|
||||||
@@ -256,7 +260,9 @@ function restartService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshMetrics() {
|
function refreshMetrics() {
|
||||||
fetch('/servers/serverId/metrics')
|
fetch('/servers/' + serverId + '/metrics', {
|
||||||
|
headers: { 'Accept': 'application/json' },
|
||||||
|
})
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success && data.data) {
|
if (data.success && data.data) {
|
||||||
@@ -303,9 +309,10 @@ function saveThresholds() {
|
|||||||
|
|
||||||
document.querySelector('#thresholdModal .btn-primary').disabled = true;
|
document.querySelector('#thresholdModal .btn-primary').disabled = true;
|
||||||
|
|
||||||
fetch('/servers/serverId/thresholds', {
|
fetch('/servers/' + serverId + '/thresholds', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-CSRF-Token': getCsrfToken(),
|
'X-CSRF-Token': getCsrfToken(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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() {
|
function executeCommand() {
|
||||||
const command = terminalInput.value.trim();
|
const command = terminalInput.value.trim();
|
||||||
if (!command) return;
|
if (!command) return;
|
||||||
@@ -27,6 +33,7 @@ function executeCommand() {
|
|||||||
fetch('/terminal/' + serverId + '/execute', {
|
fetch('/terminal/' + serverId + '/execute', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-CSRF-Token': getCsrfToken(),
|
'X-CSRF-Token': getCsrfToken(),
|
||||||
},
|
},
|
||||||
@@ -45,6 +52,9 @@ function executeCommand() {
|
|||||||
if (data.stderr) {
|
if (data.stderr) {
|
||||||
appendTerminalLine(data.stderr, 'error');
|
appendTerminalLine(data.stderr, 'error');
|
||||||
}
|
}
|
||||||
|
if (data.history) {
|
||||||
|
appendHistoryRow(data.history);
|
||||||
|
}
|
||||||
updateStatus('connected', 'Connected');
|
updateStatus('connected', 'Connected');
|
||||||
} else {
|
} else {
|
||||||
appendTerminalLine(data.message || 'Command failed', 'error');
|
appendTerminalLine(data.message || 'Command failed', 'error');
|
||||||
@@ -83,6 +93,23 @@ function clearTerminal() {
|
|||||||
terminalOutput.innerHTML = '';
|
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 =
|
||||||
|
'<td>' + time + '</td>' +
|
||||||
|
'<td><code>' + escapeHtml(h.command) + '</code></td>' +
|
||||||
|
'<td><span class="badge ' + (h.exit_status === 0 ? 'badge-success' : 'badge-danger') + '">' + (h.exit_status === 0 ? 'OK' : 'ERR') + '</span></td>' +
|
||||||
|
'<td>' + escapeHtml(h.username || 'System') + '</td>';
|
||||||
|
tbody.prepend(row);
|
||||||
|
const container = tbody.closest('.table-responsive');
|
||||||
|
if (container) container.scrollTop = 0;
|
||||||
|
}
|
||||||
|
|
||||||
function clearHistory() {
|
function clearHistory() {
|
||||||
if (!confirm('Clear all command history for this server?')) return;
|
if (!confirm('Clear all command history for this server?')) return;
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ class TerminalController
|
|||||||
$ssh->disconnect();
|
$ssh->disconnect();
|
||||||
|
|
||||||
$commandHistory = new CommandHistory();
|
$commandHistory = new CommandHistory();
|
||||||
$commandHistory->log(
|
$historyId = $commandHistory->log(
|
||||||
$id,
|
$id,
|
||||||
Session::get('user_id'),
|
Session::get('user_id'),
|
||||||
$command,
|
$command,
|
||||||
@@ -115,6 +115,13 @@ class TerminalController
|
|||||||
'output' => $result['output'],
|
'output' => $result['output'],
|
||||||
'exit_status' => $result['exit_status'],
|
'exit_status' => $result['exit_status'],
|
||||||
'stderr' => $result['stderr'],
|
'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) {
|
} catch (\Throwable $e) {
|
||||||
$this->view->json([
|
$this->view->json([
|
||||||
@@ -122,6 +129,7 @@ class TerminalController
|
|||||||
'message' => 'Failed to execute command.',
|
'message' => 'Failed to execute command.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function history(int $id): void
|
public function history(int $id): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class View
|
|||||||
private string $layout = 'main';
|
private string $layout = 'main';
|
||||||
private array $data = [];
|
private array $data = [];
|
||||||
private Security $security;
|
private Security $security;
|
||||||
|
private ?string $csrfTokenCache = null;
|
||||||
|
|
||||||
public function __construct(Security $security)
|
public function __construct(Security $security)
|
||||||
{
|
{
|
||||||
@@ -80,7 +81,10 @@ class View
|
|||||||
|
|
||||||
public function csrfToken(): string
|
public function csrfToken(): string
|
||||||
{
|
{
|
||||||
return $this->security->generateCSRFToken();
|
if ($this->csrfTokenCache === null) {
|
||||||
|
$this->csrfTokenCache = $this->security->generateCSRFToken();
|
||||||
|
}
|
||||||
|
return $this->csrfTokenCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function csrfField(): string
|
public function csrfField(): string
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class CommandHistory
|
|||||||
'command' => $command,
|
'command' => $command,
|
||||||
'output' => mb_substr($output ?? '', 0, 65535),
|
'output' => mb_substr($output ?? '', 0, 65535),
|
||||||
'exit_status' => $exitStatus,
|
'exit_status' => $exitStatus,
|
||||||
|
'status' => 'active',
|
||||||
'executed_at' => date('Y-m-d H:i:s'),
|
'executed_at' => date('Y-m-d H:i:s'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user