sdl3/examples/ServerManager/UI/ServerListTab.php
2025-11-12 11:56:45 +01:00

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 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 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;
}
}