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