278 lines
11 KiB
PHP
278 lines
11 KiB
PHP
<?php
|
|
|
|
namespace ServerManager\UI;
|
|
|
|
use PHPNative\Tailwind\Data\Icon as IconName;
|
|
use PHPNative\Ui\Widget\Button;
|
|
use PHPNative\Ui\Widget\Container;
|
|
use PHPNative\Ui\Widget\Icon;
|
|
use PHPNative\Ui\Widget\Label;
|
|
use PHPNative\Ui\Widget\TabContainer;
|
|
use PHPNative\Ui\Widget\Table;
|
|
use PHPNative\Ui\Widget\TextInput;
|
|
use ServerManager\Services\HetznerService;
|
|
|
|
class ServerListTab
|
|
{
|
|
private Container $tab;
|
|
private Table $table;
|
|
private TextInput $searchInput;
|
|
private Label $statusLabel;
|
|
private Button $refreshButton;
|
|
private Button $sftpButton;
|
|
private Button $sshTerminalButton;
|
|
|
|
public array $currentServerData = [];
|
|
public null|array $selectedServer = null;
|
|
|
|
private Label $detailId;
|
|
private Label $detailName;
|
|
private Label $detailStatus;
|
|
private Label $detailType;
|
|
private Label $detailIpv4;
|
|
|
|
public function __construct(
|
|
string &$apiKey,
|
|
string &$privateKeyPath,
|
|
TabContainer $tabContainer,
|
|
Label $statusLabel,
|
|
) {
|
|
$this->statusLabel = $statusLabel;
|
|
$currentApiKey = &$apiKey;
|
|
$currentPrivateKeyPath = &$privateKeyPath;
|
|
|
|
// Create main tab container
|
|
$this->tab = new Container('flex flex-row p-4 gap-4');
|
|
|
|
// Left side: Table with search and refresh
|
|
$leftSide = new Container('flex flex-col gap-2 flex-1');
|
|
|
|
// Refresh button
|
|
$this->refreshButton = new Button(
|
|
'Server aktualisieren',
|
|
'flex shadow-lg/50 flex-row gap-2 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700',
|
|
);
|
|
$refreshIcon = new Icon(IconName::sync, 16, 'text-white');
|
|
$this->refreshButton->setIcon($refreshIcon);
|
|
$leftSide->addComponent($this->refreshButton);
|
|
|
|
// Search input
|
|
$this->searchInput = new TextInput(
|
|
'Suche...',
|
|
'w-full border border-gray-300 rounded px-3 py-2 bg-white text-black mb-2',
|
|
);
|
|
$leftSide->addComponent($this->searchInput);
|
|
|
|
// Table
|
|
$this->table = new Table(style: ' flex-1');
|
|
$this->table->setColumns([
|
|
['key' => 'id', 'title' => 'ID', 'width' => 100],
|
|
['key' => 'name', 'title' => 'Name', 'width' => 400],
|
|
['key' => 'status', 'title' => 'Status', 'width' => 120],
|
|
['key' => 'type', 'title' => 'Typ', 'width' => 120],
|
|
['key' => 'ipv4', 'title' => 'IPv4'],
|
|
]);
|
|
|
|
// Load initial test data
|
|
$this->currentServerData = HetznerService::generateTestData();
|
|
$this->table->setData($this->currentServerData);
|
|
|
|
$leftSide->addComponent($this->table);
|
|
|
|
// Right side: Detail panel
|
|
$detailPanel = new Container('flex flex-col gap-3 w-120 bg-white border-2 border-gray-300 rounded p-4');
|
|
$detailTitle = new Label('Server Details', 'text-xl font-bold text-black mb-2');
|
|
$detailPanel->addComponent($detailTitle);
|
|
|
|
$this->detailId = new Label('-', 'text-sm text-gray-900 font-mono');
|
|
$this->detailName = new Label('-', 'text-lg font-semibold text-black');
|
|
$this->detailStatus = new Label('-', 'text-sm text-gray-700');
|
|
$this->detailType = new Label('-', 'text-sm text-gray-700');
|
|
$this->detailIpv4 = new Label('-', 'text-sm text-gray-700 font-mono');
|
|
|
|
$detailPanel->addComponent(new Label('ID:', 'text-xs text-gray-500'));
|
|
$detailPanel->addComponent($this->detailId);
|
|
$detailPanel->addComponent(new Label('Name:', 'text-xs text-gray-500 mt-2'));
|
|
$detailPanel->addComponent($this->detailName);
|
|
$detailPanel->addComponent(new Label('Status:', 'text-xs text-gray-500 mt-2'));
|
|
$detailPanel->addComponent($this->detailStatus);
|
|
$detailPanel->addComponent(new Label('Typ:', 'text-xs text-gray-500 mt-2'));
|
|
$detailPanel->addComponent($this->detailType);
|
|
$detailPanel->addComponent(new Label('IPv4:', 'text-xs text-gray-500 mt-2'));
|
|
$detailPanel->addComponent($this->detailIpv4);
|
|
|
|
// SFTP Manager Button (handler will be set by SftpManagerTab)
|
|
$this->sftpButton = new Button(
|
|
'SFTP Manager öffnen',
|
|
'w-full border border-gray-300 rounded px-3 py-2 flex shadow-lg/50 shadow-cyan-500 flex-row gap-2 bg-green-300 text-black mb-2',
|
|
);
|
|
$sftpIcon = new Icon(IconName::folder, 16, 'text-white');
|
|
$this->sftpButton->setIcon($sftpIcon);
|
|
|
|
$detailPanel->addComponent($this->sftpButton);
|
|
|
|
// SSH Terminal Button
|
|
$this->sshTerminalButton = new Button(
|
|
'SSH Terminal öffnen',
|
|
'w-full border border-gray-300 rounded px-3 py-2 flex flex-row gap-2 bg-lime-300 text-black mb-2',
|
|
);
|
|
$sshTerminalIcon = new Icon(IconName::terminal, 16, 'text-white');
|
|
$this->sshTerminalButton->setIcon($sshTerminalIcon);
|
|
|
|
$serverListTab = $this;
|
|
$this->sshTerminalButton->setOnClick(function () use ($serverListTab, &$currentPrivateKeyPath) {
|
|
if ($serverListTab->selectedServer === null) {
|
|
$serverListTab->statusLabel->setText('Kein Server ausgewählt');
|
|
return;
|
|
}
|
|
|
|
if (empty($currentPrivateKeyPath) || !file_exists($currentPrivateKeyPath)) {
|
|
$serverListTab->statusLabel->setText('Private Key Pfad nicht konfiguriert');
|
|
return;
|
|
}
|
|
|
|
$host = $serverListTab->selectedServer['ipv4'];
|
|
$keyPath = escapeshellarg($currentPrivateKeyPath);
|
|
$sshCommand = "ssh -i {$keyPath} root@{$host}";
|
|
|
|
$terminals = [
|
|
'gnome-terminal -- ' . $sshCommand,
|
|
'konsole -e ' . $sshCommand,
|
|
'xterm -e ' . $sshCommand,
|
|
'x-terminal-emulator -e ' . $sshCommand,
|
|
];
|
|
|
|
$opened = false;
|
|
foreach ($terminals as $terminalCmd) {
|
|
exec($terminalCmd . ' > /dev/null 2>&1 &', $output, $returnCode);
|
|
if ($returnCode === 0) {
|
|
$opened = true;
|
|
$serverListTab->statusLabel->setText(
|
|
'SSH Terminal geöffnet für ' . $serverListTab->selectedServer['name'],
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$opened) {
|
|
$serverListTab->statusLabel->setText('Konnte kein Terminal öffnen. SSH Befehl: ' . $sshCommand);
|
|
}
|
|
});
|
|
|
|
$detailPanel->addComponent($this->sshTerminalButton);
|
|
|
|
$this->tab->addComponent($leftSide);
|
|
$this->tab->addComponent($detailPanel);
|
|
|
|
// Setup event handlers
|
|
$this->setupEventHandlers($currentApiKey, $currentPrivateKeyPath);
|
|
}
|
|
|
|
private function setupEventHandlers(string &$currentApiKey, string &$currentPrivateKeyPath): void
|
|
{
|
|
// Table row selection
|
|
$serverListTab = $this;
|
|
$this->table->setOnRowSelect(function ($index, $row) use ($serverListTab) {
|
|
if ($row) {
|
|
$serverListTab->statusLabel->setText("Server: {$row['name']} - {$row['status']} ({$row['ipv4']})");
|
|
|
|
$serverListTab->detailId->setText("#{$row['id']}");
|
|
$serverListTab->detailName->setText($row['name']);
|
|
$serverListTab->detailStatus->setText($row['status']);
|
|
$serverListTab->detailType->setText($row['type']);
|
|
$serverListTab->detailIpv4->setText($row['ipv4']);
|
|
|
|
$serverListTab->selectedServer = $row;
|
|
}
|
|
});
|
|
|
|
// Search functionality
|
|
$this->searchInput->setOnChange(function ($value) use ($serverListTab) {
|
|
$searchTerm = strtolower(trim($value));
|
|
|
|
if (empty($searchTerm)) {
|
|
$serverListTab->table->setData($serverListTab->currentServerData);
|
|
} else {
|
|
$filteredData = array_filter($serverListTab->currentServerData, function ($row) use ($searchTerm) {
|
|
return str_contains(strtolower($row['name']), $searchTerm);
|
|
});
|
|
$serverListTab->table->setData(array_values($filteredData));
|
|
}
|
|
});
|
|
|
|
// Refresh button - use reference to apiKey variable
|
|
$this->refreshButton->setOnClickAsync(
|
|
function () use (&$currentApiKey) {
|
|
try {
|
|
if (empty($currentApiKey)) {
|
|
return ['error' => 'Kein API-Key konfiguriert'];
|
|
}
|
|
|
|
$hetznerClient = new \LKDev\HetznerCloud\HetznerAPIClient($currentApiKey);
|
|
$servers = [];
|
|
|
|
foreach ($hetznerClient->servers()->all() as $server) {
|
|
$servers[] = [
|
|
'id' => $server->id,
|
|
'name' => $server->name,
|
|
'status' => $server->status,
|
|
'type' => $server->serverType->name,
|
|
'ipv4' => $server->publicNet->ipv4->ip,
|
|
];
|
|
}
|
|
|
|
return [
|
|
'success' => true,
|
|
'servers' => $servers,
|
|
'count' => count($servers),
|
|
];
|
|
} catch (\Exception $e) {
|
|
return ['error' => 'Exception: ' . $e->getMessage()];
|
|
}
|
|
},
|
|
function ($result) use ($serverListTab) {
|
|
if (is_array($result)) {
|
|
if (isset($result['error'])) {
|
|
$serverListTab->statusLabel->setText('Fehler: ' . $result['error']);
|
|
echo "Error: {$result['error']}\n";
|
|
} elseif (isset($result['success'], $result['servers'])) {
|
|
$serverListTab->currentServerData = $result['servers'];
|
|
|
|
$searchTerm = strtolower(trim($serverListTab->searchInput->getValue()));
|
|
if (empty($searchTerm)) {
|
|
$serverListTab->table->setData($serverListTab->currentServerData);
|
|
} else {
|
|
$filteredData = array_filter($serverListTab->currentServerData, function ($row) use (
|
|
$searchTerm,
|
|
) {
|
|
return str_contains(strtolower($row['name']), $searchTerm);
|
|
});
|
|
$serverListTab->table->setData(array_values($filteredData));
|
|
}
|
|
|
|
$serverListTab->statusLabel->setText('Server geladen: ' . $result['count'] . ' gefunden');
|
|
echo "Success: {$result['count']} servers loaded\n";
|
|
}
|
|
}
|
|
},
|
|
function ($error) use ($serverListTab) {
|
|
$errorMsg = is_object($error) && method_exists($error, 'getMessage')
|
|
? $error->getMessage()
|
|
: ((string) $error);
|
|
$serverListTab->statusLabel->setText('Async Fehler: ' . $errorMsg);
|
|
echo "Async error: {$errorMsg}\n";
|
|
},
|
|
);
|
|
}
|
|
|
|
public function getContainer(): Container
|
|
{
|
|
return $this->tab;
|
|
}
|
|
|
|
public function getSftpButton(): Button
|
|
{
|
|
return $this->sftpButton;
|
|
}
|
|
}
|