Backup
This commit is contained in:
parent
402ad74582
commit
2d631411cb
@ -66,7 +66,10 @@ $mainContainer = new Container('flex flex-col bg-gray-100');
|
||||
|
||||
// Modal dialog setup (hidden by default)
|
||||
$apiKeyInput = new TextInput('API Key', 'w-full border border-gray-300 rounded px-3 py-2 bg-white text-black');
|
||||
$privateKeyPathInput = new TextInput('Private Key Path', 'w-full border border-gray-300 rounded px-3 py-2 bg-white text-black');
|
||||
$privateKeyPathInput = new TextInput(
|
||||
'Private Key Path',
|
||||
'w-full border border-gray-300 rounded px-3 py-2 bg-white text-black',
|
||||
);
|
||||
|
||||
$modalDialog = new Container('bg-white rounded-lg p-6 flex flex-col w-96 gap-3');
|
||||
$modalDialog->addComponent(new Label('API Einstellungen', 'text-xl font-bold text-black'));
|
||||
@ -115,7 +118,14 @@ $fileMenu->addItem('Beenden', function () use ($app) {
|
||||
|
||||
// Settings Menu
|
||||
$settingsMenu = new Menu(title: 'Einstellungen');
|
||||
$settingsMenu->addItem('Optionen', function () use ($menuBar, $modal, $apiKeyInput, $privateKeyPathInput, &$currentApiKey, &$currentPrivateKeyPath) {
|
||||
$settingsMenu->addItem('Optionen', function () use (
|
||||
$menuBar,
|
||||
$modal,
|
||||
$apiKeyInput,
|
||||
$privateKeyPathInput,
|
||||
&$currentApiKey,
|
||||
&$currentPrivateKeyPath,
|
||||
) {
|
||||
$menuBar->closeAllMenus();
|
||||
$apiKeyInput->setValue($currentApiKey);
|
||||
$privateKeyPathInput->setValue($currentPrivateKeyPath);
|
||||
@ -147,6 +157,10 @@ $refreshButton->setIcon($refreshIcon);
|
||||
|
||||
$leftSide->addComponent($refreshButton);
|
||||
|
||||
// Search input field
|
||||
$searchInput = new TextInput('Suche...', 'w-full border border-gray-300 rounded px-3 py-2 bg-white text-black mb-2');
|
||||
$leftSide->addComponent($searchInput);
|
||||
|
||||
$table = new Table(style: ' flex-1'); // flex-1 to fill available height but not expand infinitely
|
||||
|
||||
$table->setColumns([
|
||||
@ -168,7 +182,26 @@ for ($i = 1; $i <= 63; $i++) {
|
||||
'ipv4' => sprintf('192.168.%d.%d', floor($i / 255), $i % 255),
|
||||
];
|
||||
}
|
||||
$table->setData($testData);
|
||||
|
||||
// Store current server data (will be updated when API loads servers)
|
||||
$currentServerData = $testData;
|
||||
$table->setData($currentServerData);
|
||||
|
||||
// Add search functionality
|
||||
$searchInput->setOnChange(function ($value) use ($table, &$currentServerData) {
|
||||
$searchTerm = strtolower(trim($value));
|
||||
|
||||
if (empty($searchTerm)) {
|
||||
// Show all data if search is empty
|
||||
$table->setData($currentServerData);
|
||||
} else {
|
||||
// Filter by name
|
||||
$filteredData = array_filter($currentServerData, function ($row) use ($searchTerm) {
|
||||
return str_contains(strtolower($row['name']), $searchTerm);
|
||||
});
|
||||
$table->setData(array_values($filteredData));
|
||||
}
|
||||
});
|
||||
|
||||
$leftSide->addComponent($table);
|
||||
$tab1->addComponent($leftSide);
|
||||
@ -197,11 +230,71 @@ $detailPanel->addComponent(new Label('IPv4:', 'text-xs text-gray-500 mt-2'));
|
||||
$detailPanel->addComponent($detailIpv4);
|
||||
|
||||
// SFTP Manager Button
|
||||
$sftpButton = new Button('SFTP Manager öffnen', 'w-full mt-4 px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 flex items-center justify-center');
|
||||
$sftpButton = new Button(
|
||||
'SFTP Manager öffnen',
|
||||
'w-full mt-4 px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 flex items-center justify-center',
|
||||
);
|
||||
$sftpIcon = new Icon(IconName::home, 18, 'text-white mr-2');
|
||||
$sftpButton->setIcon($sftpIcon);
|
||||
|
||||
// Add synchronous click handler to switch tab immediately
|
||||
$sftpButton->setOnClick(function () use ($tabContainer) {
|
||||
$tabContainer->setActiveTab(3); // Switch to SFTP Manager tab immediately
|
||||
});
|
||||
|
||||
$detailPanel->addComponent($sftpButton);
|
||||
|
||||
// SSH Terminal Button
|
||||
$sshTerminalButton = new Button(
|
||||
'SSH Terminal öffnen',
|
||||
'w-full mt-2 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex items-center justify-center',
|
||||
);
|
||||
$sshTerminalIcon = new Icon(IconName::home, 18, 'text-white mr-2');
|
||||
$sshTerminalButton->setIcon($sshTerminalIcon);
|
||||
|
||||
// Add click handler to open SSH terminal
|
||||
$sshTerminalButton->setOnClick(function () use (&$selectedServer, &$currentPrivateKeyPath, &$statusLabel) {
|
||||
if ($selectedServer === null) {
|
||||
$statusLabel->setText('Kein Server ausgewählt');
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($currentPrivateKeyPath) || !file_exists($currentPrivateKeyPath)) {
|
||||
$statusLabel->setText('Private Key Pfad nicht konfiguriert');
|
||||
return;
|
||||
}
|
||||
|
||||
// Build SSH command with private key
|
||||
$host = $selectedServer['ipv4'];
|
||||
$keyPath = escapeshellarg($currentPrivateKeyPath);
|
||||
$sshCommand = "ssh -i {$keyPath} root@{$host}";
|
||||
|
||||
// Try to open terminal with SSH connection
|
||||
// Different terminal emulators for different systems
|
||||
$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;
|
||||
$statusLabel->setText('SSH Terminal geöffnet für ' . $selectedServer['name']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$opened) {
|
||||
$statusLabel->setText('Konnte kein Terminal öffnen. SSH Befehl: ' . $sshCommand);
|
||||
}
|
||||
});
|
||||
|
||||
$detailPanel->addComponent($sshTerminalButton);
|
||||
|
||||
$tab1->addComponent($detailPanel);
|
||||
|
||||
// Row selection handler - update detail panel
|
||||
@ -209,7 +302,15 @@ $statusLabel = new Label(
|
||||
text: 'Fenster: ' . $window->getViewport()->windowWidth . 'x' . $window->getViewport()->windowHeight,
|
||||
style: 'basis-4/8 text-black',
|
||||
);
|
||||
$table->setOnRowSelect(function ($index, $row) use (&$statusLabel, &$selectedServer, $detailId, $detailName, $detailStatus, $detailType, $detailIpv4) {
|
||||
$table->setOnRowSelect(function ($index, $row) use (
|
||||
&$statusLabel,
|
||||
&$selectedServer,
|
||||
$detailId,
|
||||
$detailName,
|
||||
$detailStatus,
|
||||
$detailType,
|
||||
$detailIpv4,
|
||||
) {
|
||||
if ($row) {
|
||||
$statusLabel->setText("Server: {$row['name']} - {$row['status']} ({$row['ipv4']})");
|
||||
|
||||
@ -260,14 +361,29 @@ $loadServersAsync = function () use ($currentApiKey) {
|
||||
// Configure refresh button to load servers asynchronously
|
||||
$refreshButton->setOnClickAsync(
|
||||
$loadServersAsync,
|
||||
function ($result) use ($table, $statusLabel) {
|
||||
function ($result) use ($table, $statusLabel, &$currentServerData, $searchInput) {
|
||||
// Handle the result in the main thread (can access objects here)
|
||||
if (is_array($result)) {
|
||||
if (isset($result['error'])) {
|
||||
$statusLabel->setText('Fehler: ' . $result['error']);
|
||||
echo "Error: {$result['error']}\n";
|
||||
} elseif (isset($result['success'], $result['servers'])) {
|
||||
$table->setData($result['servers']);
|
||||
// Update current server data
|
||||
$currentServerData = $result['servers'];
|
||||
|
||||
// Check if search is active
|
||||
$searchTerm = strtolower(trim($searchInput->getValue()));
|
||||
if (empty($searchTerm)) {
|
||||
// No search, show all servers
|
||||
$table->setData($currentServerData);
|
||||
} else {
|
||||
// Apply search filter to new data
|
||||
$filteredData = array_filter($currentServerData, function ($row) use ($searchTerm) {
|
||||
return str_contains(strtolower($row['name']), $searchTerm);
|
||||
});
|
||||
$table->setData(array_values($filteredData));
|
||||
}
|
||||
|
||||
$statusLabel->setText('Server geladen: ' . $result['count'] . ' gefunden');
|
||||
echo "Success: {$result['count']} servers loaded\n";
|
||||
}
|
||||
@ -282,19 +398,7 @@ $refreshButton->setOnClickAsync(
|
||||
|
||||
$tabContainer->addTab('Server', $tab1);
|
||||
|
||||
// Tab 2: Some info
|
||||
$tab2 = new Container('flex flex-col p-4');
|
||||
$tab2->addComponent(new Label('Dies ist Tab 2', 'text-xl font-bold mb-4'));
|
||||
$tab2->addComponent(new Label('Hier könnte weiterer Inhalt stehen...', ''));
|
||||
$tabContainer->addTab('Info', $tab2);
|
||||
|
||||
// Tab 3: Settings
|
||||
$tab3 = new Container('flex flex-col p-4');
|
||||
$tab3->addComponent(new Label('Einstellungen', 'text-xl font-bold mb-4'));
|
||||
$tab3->addComponent(new Label('Konfigurationsoptionen...', ''));
|
||||
$tabContainer->addTab('Einstellungen', $tab3);
|
||||
|
||||
// Tab 4: SFTP Manager (will be populated dynamically when server is selected)
|
||||
// Tab 2: SFTP Manager (will be populated dynamically when server is selected)
|
||||
$sftpTab = new Container('flex flex-row p-4 gap-4 bg-gray-50');
|
||||
|
||||
// Left side: Local file browser
|
||||
@ -318,6 +422,80 @@ $sftpTab->addComponent($remoteBrowserContainer);
|
||||
|
||||
$tabContainer->addTab('SFTP Manager', $sftpTab);
|
||||
|
||||
// Remote FileBrowser navigation handler - load directory asynchronously
|
||||
$remoteFileBrowser->setOnFileSelect(function ($path, $row) use (
|
||||
$remoteFileBrowser,
|
||||
&$selectedServer,
|
||||
&$currentPrivateKeyPath,
|
||||
&$statusLabel,
|
||||
) {
|
||||
if (!isset($row['isDir']) || !$row['isDir']) {
|
||||
return; // Only handle directories
|
||||
}
|
||||
|
||||
// Load directory asynchronously via Button with async handler
|
||||
$loadButton = new Button('Load', '');
|
||||
$loadButton->setOnClickAsync(
|
||||
function () use ($path, &$selectedServer, &$currentPrivateKeyPath) {
|
||||
if ($selectedServer === null || empty($currentPrivateKeyPath)) {
|
||||
return ['error' => 'Not connected'];
|
||||
}
|
||||
|
||||
try {
|
||||
$sftp = new \phpseclib3\Net\SFTP($selectedServer['ipv4']);
|
||||
$key = \phpseclib3\Crypt\PublicKeyLoader::load(file_get_contents($currentPrivateKeyPath));
|
||||
|
||||
if (!$sftp->login('root', $key)) {
|
||||
return ['error' => 'SFTP Login failed'];
|
||||
}
|
||||
|
||||
$files = $sftp->nlist($path);
|
||||
if ($files === false) {
|
||||
return ['error' => 'Cannot read directory'];
|
||||
}
|
||||
|
||||
$fileList = [];
|
||||
foreach ($files as $file) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
$fullPath = rtrim($path, '/') . '/' . $file;
|
||||
$stat = $sftp->stat($fullPath);
|
||||
$fileList[] = [
|
||||
'name' => $file,
|
||||
'path' => $fullPath,
|
||||
'isDir' => ($stat['type'] ?? 0) === 2,
|
||||
'size' => $stat['size'] ?? 0,
|
||||
'mtime' => $stat['mtime'] ?? 0,
|
||||
];
|
||||
}
|
||||
|
||||
return ['success' => true, 'path' => $path, 'files' => $fileList];
|
||||
} catch (\Exception $e) {
|
||||
return ['error' => $e->getMessage()];
|
||||
}
|
||||
},
|
||||
function ($result) use ($remoteFileBrowser, &$statusLabel) {
|
||||
if (isset($result['error'])) {
|
||||
$statusLabel->setText('SFTP Fehler: ' . $result['error']);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($result['success'])) {
|
||||
$remoteFileBrowser->setPath($result['path']);
|
||||
$remoteFileBrowser->setFileData($result['files']);
|
||||
}
|
||||
},
|
||||
function ($error) use (&$statusLabel) {
|
||||
$errorMsg = is_string($error) ? $error : 'Unknown error';
|
||||
$statusLabel->setText('SFTP Error: ' . $errorMsg);
|
||||
},
|
||||
);
|
||||
|
||||
// Trigger the async load
|
||||
$loadButton->handleMouseClick(0, 0, 0);
|
||||
});
|
||||
|
||||
// SFTP Button Click Handler - Connect to server and load remote files
|
||||
$sftpButton->setOnClickAsync(
|
||||
function () use (&$selectedServer, &$currentPrivateKeyPath) {
|
||||
@ -382,7 +560,9 @@ $sftpButton->setOnClickAsync(
|
||||
}
|
||||
|
||||
if (isset($result['success']) && $result['success']) {
|
||||
$connectionStatusLabel->setText('Verbunden mit: ' . $result['server']['name'] . ' (' . $result['server']['ipv4'] . ')');
|
||||
$connectionStatusLabel->setText(
|
||||
'Verbunden mit: ' . $result['server']['name'] . ' (' . $result['server']['ipv4'] . ')',
|
||||
);
|
||||
$statusLabel->setText('SFTP Verbindung erfolgreich zu ' . $result['server']['name']);
|
||||
|
||||
// Populate remote file browser with the file list
|
||||
@ -390,11 +570,13 @@ $sftpButton->setOnClickAsync(
|
||||
$remoteFileBrowser->setFileData($result['files']);
|
||||
|
||||
// Switch to SFTP Manager tab
|
||||
$tabContainer->setActiveTab(3); // Index 3 = SFTP Manager tab
|
||||
$tabContainer->setActiveTab(1); // Index 3 = SFTP Manager tab
|
||||
}
|
||||
},
|
||||
function ($error) use (&$statusLabel) {
|
||||
$errorMsg = is_string($error) ? $error : (is_object($error) && method_exists($error, 'getMessage') ? $error->getMessage() : 'Unbekannter Fehler');
|
||||
$errorMsg = is_string($error)
|
||||
? $error
|
||||
: (is_object($error) && method_exists($error, 'getMessage') ? $error->getMessage() : 'Unbekannter Fehler');
|
||||
$statusLabel->setText('SFTP Async Fehler: ' . $errorMsg);
|
||||
},
|
||||
);
|
||||
@ -424,7 +606,16 @@ $cancelButton->setOnClick(function () use ($menuBar, $modal) {
|
||||
$modal->setVisible(false);
|
||||
});
|
||||
|
||||
$saveButton->setOnClick(function () use ($settings, &$currentApiKey, &$currentPrivateKeyPath, $apiKeyInput, $privateKeyPathInput, $menuBar, $modal, &$statusLabel) {
|
||||
$saveButton->setOnClick(function () use (
|
||||
$settings,
|
||||
&$currentApiKey,
|
||||
&$currentPrivateKeyPath,
|
||||
$apiKeyInput,
|
||||
$privateKeyPathInput,
|
||||
$menuBar,
|
||||
$modal,
|
||||
&$statusLabel,
|
||||
) {
|
||||
$currentApiKey = trim($apiKeyInput->getValue());
|
||||
$currentPrivateKeyPath = trim($privateKeyPathInput->getValue());
|
||||
|
||||
|
||||
30
src/Ui/Widget/ClickableHeaderLabel.php
Normal file
30
src/Ui/Widget/ClickableHeaderLabel.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Ui\Widget;
|
||||
|
||||
class ClickableHeaderLabel extends Label
|
||||
{
|
||||
private string $columnKey;
|
||||
private Table $table;
|
||||
|
||||
public function __construct(string $columnKey, Table $table, string $title, string $style)
|
||||
{
|
||||
$this->columnKey = $columnKey;
|
||||
$this->table = $table;
|
||||
parent::__construct($title, $style);
|
||||
}
|
||||
|
||||
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
||||
{
|
||||
if (
|
||||
$mouseX >= $this->viewport->x &&
|
||||
$mouseX <= ($this->viewport->x + $this->viewport->width) &&
|
||||
$mouseY >= $this->viewport->y &&
|
||||
$mouseY <= ($this->viewport->y + $this->viewport->height)
|
||||
) {
|
||||
$this->table->sortByColumn($this->columnKey);
|
||||
return true;
|
||||
}
|
||||
return parent::handleMouseClick($mouseX, $mouseY, $button);
|
||||
}
|
||||
}
|
||||
@ -269,5 +269,34 @@ class FileBrowser extends Container
|
||||
}
|
||||
|
||||
$this->fileTable->setData($tableData);
|
||||
|
||||
// Set up row selection handler AFTER data is set (for remote browsers)
|
||||
// This needs to be done every time because setData might reset handlers
|
||||
$this->setupRemoteNavigationHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup navigation handler for remote file browser
|
||||
*/
|
||||
private function setupRemoteNavigationHandler(): void
|
||||
{
|
||||
if (!$this->isRemote) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fileBrowser = $this;
|
||||
$this->fileTable->setOnRowSelect(function ($index, $row) use ($fileBrowser) {
|
||||
if ($row && isset($row['isDir']) && $row['isDir'] && !empty($row['path'])) {
|
||||
// Trigger the external callback for directory navigation
|
||||
if ($fileBrowser->onFileSelect !== null) {
|
||||
($fileBrowser->onFileSelect)($row['path'], $row);
|
||||
}
|
||||
} elseif ($row && isset($row['path']) && !empty($row['path']) && !($row['isDir'] ?? false)) {
|
||||
// File selected
|
||||
if ($fileBrowser->onFileSelect !== null) {
|
||||
($fileBrowser->onFileSelect)($row['path'], $row);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,17 +48,18 @@ class Table extends Container
|
||||
$this->headerContainer->clearChildren();
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$title = $column['title'] ?? $column['key'];
|
||||
$key = $column['key'];
|
||||
$title = $column['title'] ?? $key;
|
||||
$width = $column['width'] ?? null;
|
||||
|
||||
$style = 'px-4 py-2 text-black font-bold border-r border-gray-300';
|
||||
$style = 'px-4 py-2 text-black font-bold border-r border-gray-300 hover:bg-gray-300 cursor-pointer';
|
||||
if ($width) {
|
||||
$style .= ' w-' . ((int) ($width / 4));
|
||||
} else {
|
||||
$style .= ' flex-1';
|
||||
}
|
||||
|
||||
$headerLabel = new Label($title, $style);
|
||||
$headerLabel = new ClickableHeaderLabel($key, $this, $title, $style);
|
||||
$this->headerContainer->addComponent($headerLabel);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user