sdl3/examples/ServerManager/UI/SftpManagerTab.php
2025-12-01 11:06:53 +01:00

1561 lines
61 KiB
PHP

<?php
namespace ServerManager\UI;
use PHPNative\Ui\Widget\Button;
use PHPNative\Ui\Widget\Container;
use PHPNative\Ui\Widget\FileBrowser;
use PHPNative\Ui\Widget\Icon;
use PHPNative\Ui\Widget\Label;
use PHPNative\Ui\Widget\Modal;
use PHPNative\Ui\Widget\TextArea;
use PHPNative\Ui\Widget\TextInput;
use PHPNative\Ui\Widget\ProgressBar;
class SftpManagerTab
{
private Container $tab;
private FileBrowser $localFileBrowser;
private FileBrowser $remoteFileBrowser;
private Label $connectionStatusLabel;
private Modal $editModal;
private TextArea $fileEditor;
private Label $filePathLabel;
private string $currentEditFilePath = '';
private Modal $filenameModal;
private TextInput $filenameInput;
private Modal $renameModal;
private TextInput $renameInput;
private string $currentRenameFilePath = '';
private Modal $deleteConfirmModal;
private string $currentDeleteFilePath = '';
private Label $deleteConfirmLabel;
private null|array $currentLocalSelection = null;
private null|array $currentRemoteSelection = null;
private ?string $lastRemoteClickPath = null;
private float $lastRemoteClickTime = 0.0;
private ProgressBar $transferProgressBar;
private Label $transferInfoLabel;
private array $pendingUploadQueue = [];
private int $totalUploadFiles = 0;
private int $completedUploadFiles = 0;
private array $pendingDownloadQueue = [];
private int $totalDownloadFiles = 0;
private int $completedDownloadFiles = 0;
public function __construct(
string &$apiKey,
string &$privateKeyPath,
string &$remoteStartDir,
ServerListTab $serverListTab,
\PHPNative\Ui\Widget\TabContainer $tabContainer,
Label $statusLabel,
) {
$this->tab = new Container('flex flex-row p-4 gap-4 bg-gray-50');
$currentPrivateKeyPath = &$privateKeyPath;
$currentRemoteStartDir = &$remoteStartDir;
// Left side: Local file browser
// Lokaler Browser: Spalte, Scrollen übernimmt der FileBrowser selbst
$localBrowserContainer = new Container('flex flex-col flex-1 gap-2');
$localBrowserContainer->addComponent(new Label('Lokal', 'text-lg font-bold text-black mb-2'));
$this->localFileBrowser = new FileBrowser(
getcwd(),
false,
'flex-1 bg-white border-2 border-gray-300 rounded p-2',
);
$localBrowserContainer->addComponent($this->localFileBrowser);
// Track local file selection for uploads
$sftpTab = $this;
$this->localFileBrowser->setOnFileSelect(function ($path, $row) use ($sftpTab) {
$sftpTab->currentLocalSelection = [
'path' => $path,
'row' => $row,
];
});
// Right side: Remote file browser
// Remote-Browser: Spalte, Scrollen übernimmt der FileBrowser selbst
$remoteBrowserContainer = new Container('flex flex-col flex-1 gap-2');
// Header with title and new file button
$remoteHeader = new Container('flex flex-row items-center gap-2 mb-2');
$remoteHeader->addComponent(new Label('Remote', 'text-lg font-bold text-black flex-1'));
$newFileButton = new Button('', 'px-3 py-2 bg-green-600 text-white rounded hover:bg-green-700 flex items-center justify-center');
$newFileIcon = new Icon(\PHPNative\Tailwind\Data\Icon::plus, 16, 'text-white');
$newFileButton->setIcon($newFileIcon);
$remoteHeader->addComponent($newFileButton);
$remoteBrowserContainer->addComponent($remoteHeader);
$this->remoteFileBrowser = new FileBrowser('/', true, 'flex-1 bg-white border-2 border-gray-300 rounded p-2');
$remoteBrowserContainer->addComponent($this->remoteFileBrowser);
// Connection status label
$this->connectionStatusLabel = new Label('Nicht verbunden', 'text-sm text-gray-600 italic mb-2');
$remoteBrowserContainer->addComponent($this->connectionStatusLabel);
// Middle: Transfer buttons (Upload/Download)
$transferContainer = new Container('flex flex-col justify-center items-center gap-2 w-[180]');
$uploadButton = new Button(
'Hochladen →',
'w-full px-3 py-2 bg-green-600 text-white rounded hover:bg-green-700',
);
$downloadButton = new Button(
'← Herunterladen',
'w-full px-3 py-2 bg-blue-600 text-white rounded hover:bg-blue-700',
);
$transferContainer->addComponent($uploadButton);
$transferContainer->addComponent($downloadButton);
// Transfer-Info + ProgressBar
$this->transferInfoLabel = new Label('Kein Transfer aktiv', 'text-xs text-gray-600 mt-2');
$this->transferProgressBar = new ProgressBar('mt-1');
$this->transferProgressBar->setValue(0.0);
$transferContainer->addComponent($this->transferInfoLabel);
$transferContainer->addComponent($this->transferProgressBar);
$this->tab->addComponent($localBrowserContainer);
$this->tab->addComponent($transferContainer);
$this->tab->addComponent($remoteBrowserContainer);
// Setup remote navigation handler
$this->setupRemoteNavigationHandler($currentPrivateKeyPath, $serverListTab, $statusLabel);
// Setup SFTP connection handler
$this->setupSftpConnectionHandler($currentPrivateKeyPath, $currentRemoteStartDir, $serverListTab, $tabContainer, $statusLabel);
// Create edit modal
$this->createEditModal($currentPrivateKeyPath, $serverListTab, $statusLabel);
// Create filename input modal
$this->createFilenameModal($currentPrivateKeyPath, $serverListTab, $statusLabel);
// Create rename modal
$this->createRenameModal($currentPrivateKeyPath, $serverListTab, $statusLabel);
// Create delete confirmation modal
$this->createDeleteConfirmModal($currentPrivateKeyPath, $serverListTab, $statusLabel);
// Setup transfer button handlers
$uploadButton->setOnClick(function () use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel) {
$sftpTab->handleUpload($currentPrivateKeyPath, $serverListTab, $statusLabel);
});
$downloadButton->setOnClick(function () use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel) {
$sftpTab->handleDownload($currentPrivateKeyPath, $serverListTab, $statusLabel);
});
// Setup file edit handler
$this->remoteFileBrowser->setOnEditFile(function ($path, $row) use (
&$currentPrivateKeyPath,
$serverListTab,
$statusLabel,
) {
$this->handleFileEdit($path, $row, $currentPrivateKeyPath, $serverListTab, $statusLabel);
});
// Setup new file button handler
$sftpTab = $this;
$newFileButton->setOnClick(function () use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel) {
$sftpTab->handleNewFile($currentPrivateKeyPath, $serverListTab, $statusLabel);
});
// Setup rename handler
$this->remoteFileBrowser->setOnRenameFile(function ($path, $row) use (
$sftpTab,
&$currentPrivateKeyPath,
$serverListTab,
$statusLabel,
) {
$sftpTab->handleFileRename($path, $row, $currentPrivateKeyPath, $serverListTab, $statusLabel);
});
// Setup delete handler
$this->remoteFileBrowser->setOnDeleteFile(function ($path, $row) use (
$sftpTab,
&$currentPrivateKeyPath,
$serverListTab,
$statusLabel,
) {
$sftpTab->handleFileDelete($path, $row, $currentPrivateKeyPath, $serverListTab, $statusLabel);
});
}
private function setupRemoteNavigationHandler(
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
$sftpTab = $this;
// Create reference to selectedServer array outside of closure
$selectedServerRef = &$serverListTab->selectedServer;
$this->remoteFileBrowser->setOnFileSelect(
function ($path, $row) use (
$sftpTab,
&$currentPrivateKeyPath,
&$selectedServerRef,
$statusLabel,
) {
// Track remote selection (for downloads)
$sftpTab->currentRemoteSelection = [
'path' => $path,
'row' => $row,
];
// Only navigate when a directory is selected
if (!isset($row['isDir']) || !$row['isDir']) {
return;
}
// Require double click for navigation:
// first click selektiert nur, zweiter Klick (innerhalb kurzer Zeit)
// öffnet das Verzeichnis.
$now = microtime(true);
$doubleClickThreshold = 0.4; // Sekunden
if (
$sftpTab->lastRemoteClickPath !== $path ||
($now - $sftpTab->lastRemoteClickTime) > $doubleClickThreshold
) {
$sftpTab->lastRemoteClickPath = $path;
$sftpTab->lastRemoteClickTime = $now;
return;
}
// Zweiter Klick innerhalb des Zeitfensters -> als Doppelklick werten
$sftpTab->lastRemoteClickPath = null;
$sftpTab->lastRemoteClickTime = 0.0;
$loadButton = new Button('Load', '');
$loadButton->setOnClickAsync(
function () use ($path, &$currentPrivateKeyPath, &$selectedServerRef) {
if ($selectedServerRef === null || empty($currentPrivateKeyPath)) {
return ['error' => 'Not connected'];
}
// Copy to local variable for async context
$selectedServer = $selectedServerRef;
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 ($sftpTab, $statusLabel) {
if (isset($result['error'])) {
$statusLabel->setText('SFTP Fehler: ' . $result['error']);
return;
}
if (isset($result['success'])) {
$sftpTab->remoteFileBrowser->setPath($result['path']);
$sftpTab->remoteFileBrowser->setFileData($result['files']);
}
},
function ($error) use ($statusLabel) {
$errorMsg = is_string($error) ? $error : 'Unknown error';
$statusLabel->setText('SFTP Error: ' . $errorMsg);
},
);
$loadButton->handleMouseClick(0, 0, 0);
},
);
}
private function setupSftpConnectionHandler(
string &$currentPrivateKeyPath,
string &$currentRemoteStartDir,
ServerListTab $serverListTab,
\PHPNative\Ui\Widget\TabContainer $tabContainer,
Label $statusLabel,
): void {
$sftpTab = $this;
// Create a reference to selectedServer that we can update
$selectedServerRef = &$serverListTab->selectedServer;
// Set up async handler for SFTP connection (tab switch happens in success callback)
$serverListTab->getSftpButton()->setOnClickAsync(
function () use (&$currentPrivateKeyPath, &$currentRemoteStartDir, &$selectedServerRef) {
if ($selectedServerRef === null) {
return ['error' => 'Kein Server ausgewählt'];
}
// Copy the selected server data to a local variable for use in async context
$selectedServer = $selectedServerRef;
if (empty($currentPrivateKeyPath) || !file_exists($currentPrivateKeyPath)) {
return ['error' => 'Private Key Pfad nicht konfiguriert oder Datei nicht gefunden'];
}
try {
$ssh = new \phpseclib3\Net\SSH2($selectedServer['ipv4']);
$key = \phpseclib3\Crypt\PublicKeyLoader::load(file_get_contents($currentPrivateKeyPath));
if (!$ssh->login('root', $key)) {
return ['error' => 'SSH Login fehlgeschlagen'];
}
$sftp = new \phpseclib3\Net\SFTP($selectedServer['ipv4']);
if (!$sftp->login('root', $key)) {
return ['error' => 'SFTP Login fehlgeschlagen'];
}
// Use configured start directory or fallback to root
$startDir = !empty($currentRemoteStartDir) ? $currentRemoteStartDir : '/';
// Check if start directory exists, fallback to root if not
if (!$sftp->is_dir($startDir)) {
$startDir = '/';
}
$files = $sftp->nlist($startDir);
if ($files === false) {
return ['error' => 'Kann Verzeichnis nicht lesen: ' . $startDir];
}
$fileList = [];
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
$fullPath = rtrim($startDir, '/') . '/' . $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,
'server' => $selectedServer,
'files' => $fileList,
'path' => $startDir,
];
} catch (\Exception $e) {
return ['error' => 'Verbindung fehlgeschlagen: ' . $e->getMessage()];
}
},
function ($result) use ($sftpTab, $tabContainer, $statusLabel) {
if (isset($result['error'])) {
$statusLabel->setText('SFTP Fehler: ' . $result['error']);
return;
}
if (isset($result['success']) && $result['success']) {
// Switch to SFTP tab on successful connection
// Tab-Reihenfolge in App.php:
// 0 = Server, 1 = Kanban, 2 = SFTP Manager
$tabContainer->setActiveTab(2);
$sftpTab->connectionStatusLabel->setText(
'Verbunden mit: ' . $result['server']['name'] . ' (' . $result['server']['ipv4'] . ')',
);
$statusLabel->setText('SFTP Verbindung erfolgreich zu ' . $result['server']['name']);
$sftpTab->remoteFileBrowser->setPath($result['path'] ?? '/');
$sftpTab->remoteFileBrowser->setFileData($result['files']);
}
},
function ($error) use ($statusLabel) {
$errorMsg = is_string($error)
? $error
: (
is_object($error) && method_exists($error, 'getMessage')
? $error->getMessage()
: 'Unbekannter Fehler'
);
$statusLabel->setText('SFTP Async Fehler: ' . $errorMsg);
},
);
}
public function getContainer(): Container
{
return $this->tab;
}
public function getEditModal(): Modal
{
return $this->editModal;
}
public function getFilenameModal(): Modal
{
return $this->filenameModal;
}
public function getRenameModal(): Modal
{
return $this->renameModal;
}
public function getDeleteConfirmModal(): Modal
{
return $this->deleteConfirmModal;
}
private function createEditModal(
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
// Create modal content container
$modalContent = new Container('flex flex-col bg-white rounded-lg shadow-lg p-4 gap-3 w-[600] h-[400]');
// Disable texture caching for modal content to allow dynamic content updates
$modalContent->setUseTextureCache(false);
// File path label
$this->filePathLabel = new Label('', 'text-sm text-gray-600 font-mono');
$modalContent->addComponent($this->filePathLabel);
// Text editor
$this->fileEditor = new TextArea(
'',
'Lade Datei...',
'flex-1 border-2 border-gray-300 rounded p-2 bg-white text-black font-mono text-sm',
);
// Disable texture caching for TextArea to ensure text updates are visible
$this->fileEditor->setUseTextureCache(false);
$modalContent->addComponent($this->fileEditor);
// Button container
$buttonContainer = new Container('flex flex-row justify-end gap-2');
// Cancel button
$cancelButton = new Button(
'Abbrechen',
'px-4 py-2 bg-gray-300 text-black rounded hover:bg-gray-400',
);
$sftpTab = $this;
$cancelButton->setOnClick(function () use ($sftpTab) {
$sftpTab->editModal->setVisible(false);
});
$buttonContainer->addComponent($cancelButton);
// Save button
$saveButton = new Button('Speichern', 'px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600');
$saveButton->setOnClick(function () use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel) {
$sftpTab->handleFileSave($currentPrivateKeyPath, $serverListTab, $statusLabel);
});
$buttonContainer->addComponent($saveButton);
$modalContent->addComponent($buttonContainer);
// Create modal (will be added to main container by App.php)
$this->editModal = new Modal($modalContent);
}
private function handleFileEdit(
string $path,
array $row,
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
$this->currentEditFilePath = $path;
$this->filePathLabel->setText('Bearbeite: ' . $path);
$this->fileEditor->setValue('');
$this->fileEditor->setFocused(false);
$this->editModal->setVisible(true);
// Reference to components for async callback
$selectedServerRef = &$serverListTab->selectedServer;
$sftpTab = $this;
// Load file content asynchronously
$loadButton = new Button('Load', '');
$loadButton->setOnClickAsync(
function () use ($path, &$currentPrivateKeyPath, &$selectedServerRef) {
if ($selectedServerRef === null || empty($currentPrivateKeyPath)) {
return ['error' => 'Not connected'];
}
$selectedServer = $selectedServerRef;
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'];
}
$fileContent = $sftp->get($path);
if ($fileContent === false) {
return ['error' => 'Could not read file'];
}
return ['success' => true, 'content' => $fileContent];
} catch (\Exception $e) {
return ['error' => $e->getMessage()];
}
},
function ($result) use ($sftpTab, $statusLabel) {
if (isset($result['error'])) {
$statusLabel->setText('Fehler beim Laden: ' . $result['error']);
$sftpTab->editModal->setVisible(false);
return;
}
if (isset($result['success'])) {
$sftpTab->fileEditor->setValue($result['content']);
$sftpTab->fileEditor->setFocused(true);
$statusLabel->setText('Datei geladen');
}
},
function ($error) use ($statusLabel, $sftpTab) {
$errorMsg = is_string($error) ? $error : 'Unknown error';
$statusLabel->setText('Fehler: ' . $errorMsg);
$sftpTab->editModal->setVisible(false);
},
);
$loadButton->handleMouseClick(0, 0, 0);
}
private function handleFileSave(
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
$content = $this->fileEditor->getValue();
$path = $this->currentEditFilePath;
if (empty($path)) {
$statusLabel->setText('Fehler: Kein Dateipfad');
return;
}
// Reference to components for async callback
$selectedServerRef = &$serverListTab->selectedServer;
$sftpTab = $this;
// Save file content asynchronously
$saveButton = new Button('Save', '');
$saveButton->setOnClickAsync(
function () use ($path, $content, &$currentPrivateKeyPath, &$selectedServerRef) {
if ($selectedServerRef === null || empty($currentPrivateKeyPath)) {
return ['error' => 'Not connected'];
}
$selectedServer = $selectedServerRef;
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'];
}
$result = $sftp->put($path, $content);
if ($result === false) {
return ['error' => 'Could not write file'];
}
return ['success' => true];
} catch (\Exception $e) {
return ['error' => $e->getMessage()];
}
},
function ($result) use ($sftpTab, $statusLabel, &$currentPrivateKeyPath, &$selectedServerRef) {
if (isset($result['error'])) {
$statusLabel->setText('Fehler beim Speichern: ' . $result['error']);
return;
}
if (isset($result['success'])) {
$statusLabel->setText('Datei erfolgreich gespeichert');
$sftpTab->editModal->setVisible(false);
// Reload the current directory to show the new/updated file
$sftpTab->reloadCurrentDirectory($currentPrivateKeyPath, $selectedServerRef, $statusLabel);
}
},
function ($error) use ($statusLabel) {
$errorMsg = is_string($error) ? $error : 'Unknown error';
$statusLabel->setText('Fehler: ' . $errorMsg);
},
);
$saveButton->handleMouseClick(0, 0, 0);
}
private function handleNewFile(
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
if ($serverListTab->selectedServer === null) {
$statusLabel->setText('Kein Server ausgewählt');
return;
}
// Show filename input modal
$this->filenameInput->setValue('neue_datei.txt');
$this->filenameModal->setVisible(true);
}
private function createFilenameModal(
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
// Create modal content
$modalContent = new Container('flex flex-col bg-white rounded-lg shadow-lg p-4 gap-3 w-96');
// Title
$modalContent->addComponent(new Label('Neue Datei', 'text-lg font-bold text-black'));
// Filename input
$this->filenameInput = new TextInput(
'Dateiname',
'w-full border-2 border-gray-300 rounded px-3 py-2 bg-white text-black',
);
$modalContent->addComponent($this->filenameInput);
// Button container
$buttonContainer = new Container('flex flex-row justify-end gap-2');
// Cancel button
$cancelButton = new Button('Abbrechen', 'px-4 py-2 bg-gray-300 text-black rounded hover:bg-gray-400');
$sftpTab = $this;
$cancelButton->setOnClick(function () use ($sftpTab) {
$sftpTab->filenameModal->setVisible(false);
});
$buttonContainer->addComponent($cancelButton);
// Create button
$createButton = new Button('Erstellen', 'px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700');
$createButton->setOnClick(function () use ($sftpTab, $statusLabel) {
$filename = trim($sftpTab->filenameInput->getValue());
if (empty($filename)) {
$statusLabel->setText('Dateiname darf nicht leer sein');
return;
}
// Close filename modal
$sftpTab->filenameModal->setVisible(false);
// Create the new file with the given name
$currentPath = $sftpTab->remoteFileBrowser->getCurrentPath();
$newFilePath = rtrim($currentPath, '/') . '/' . $filename;
$sftpTab->currentEditFilePath = $newFilePath;
$sftpTab->filePathLabel->setText('Neue Datei: ' . $newFilePath);
$sftpTab->fileEditor->setValue('');
$sftpTab->fileEditor->setFocused(true);
$sftpTab->editModal->setVisible(true);
$statusLabel->setText('Neue Datei wird erstellt: ' . $filename);
});
$buttonContainer->addComponent($createButton);
$modalContent->addComponent($buttonContainer);
// Create modal
$this->filenameModal = new Modal($modalContent);
}
private function reloadCurrentDirectory(
string &$currentPrivateKeyPath,
?array &$selectedServerRef,
Label $statusLabel,
): void {
$path = $this->remoteFileBrowser->getCurrentPath();
if ($selectedServerRef === null || empty($currentPrivateKeyPath)) {
return;
}
$sftpTab = $this;
// Load directory asynchronously
$loadButton = new Button('Load', '');
$loadButton->setOnClickAsync(
function () use ($path, &$currentPrivateKeyPath, &$selectedServerRef) {
if ($selectedServerRef === null || empty($currentPrivateKeyPath)) {
return ['error' => 'Not connected'];
}
$selectedServer = $selectedServerRef;
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 ($sftpTab, $statusLabel) {
if (isset($result['error'])) {
$statusLabel->setText('Fehler beim Aktualisieren: ' . $result['error']);
return;
}
if (isset($result['success'])) {
$sftpTab->remoteFileBrowser->setPath($result['path']);
$sftpTab->remoteFileBrowser->setFileData($result['files']);
}
},
function ($error) use ($statusLabel) {
$errorMsg = is_string($error) ? $error : 'Unknown error';
$statusLabel->setText('Fehler beim Aktualisieren: ' . $errorMsg);
},
);
$loadButton->handleMouseClick(0, 0, 0);
}
private function handleUpload(
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
if ($this->currentLocalSelection === null) {
$statusLabel->setText('Keine lokale Datei ausgewählt');
return;
}
$localRow = $this->currentLocalSelection['row'] ?? null;
$localPath = $this->currentLocalSelection['path'] ?? null;
if ($localRow === null || $localPath === null) {
$statusLabel->setText('Ungültige lokale Auswahl');
return;
}
if ($serverListTab->selectedServer === null) {
$statusLabel->setText('Kein Server ausgewählt');
return;
}
if (empty($currentPrivateKeyPath) || !file_exists($currentPrivateKeyPath)) {
$statusLabel->setText('Private Key Pfad nicht konfiguriert oder Datei nicht gefunden');
return;
}
$remoteDir = $this->remoteFileBrowser->getCurrentPath();
if ($remoteDir === '') {
$remoteDir = '/';
}
// Upload-Größe vorab bestimmen
$stats = $this->calculateLocalUploadStats($localPath, $localRow);
$totalBytes = $stats['bytes'];
$fileCount = $stats['files'];
$dirCount = $stats['dirs'];
$this->transferProgressBar->setValue(0.0);
$this->transferInfoLabel->setText(
sprintf(
'Upload: %d Dateien, %d Ordner (%.2f MB)',
$fileCount,
$dirCount,
$totalBytes > 0 ? ($totalBytes / (1024 * 1024)) : 0,
),
);
// Upload-Queue für virtuelle ProgressBar vorbereiten
$this->pendingUploadQueue = $this->buildUploadQueue($localPath, $localRow, $remoteDir);
$this->totalUploadFiles = count($this->pendingUploadQueue);
$this->completedUploadFiles = 0;
if ($this->totalUploadFiles <= 0) {
$this->transferProgressBar->setValue(1.0);
$this->transferInfoLabel->setText('Keine Dateien zum Hochladen');
return;
}
// Referenz auf ausgewählten Server für spätere Reloads
$this->startNextUploadTask($currentPrivateKeyPath, $serverListTab, $statusLabel);
}
/**
* Lokale Upload-Statistik (Dateien/Ordner/Bytes) rekursiv berechnen.
*/
private function calculateLocalUploadStats(string $path, array $row): array
{
$files = 0;
$dirs = 0;
$bytes = 0;
$isDir = (bool) ($row['isDir'] ?? false);
if (!$isDir) {
if (is_file($path) && is_readable($path)) {
$files = 1;
$bytes = (int) @filesize($path);
}
return ['files' => $files, 'dirs' => $dirs, 'bytes' => $bytes];
}
// Ordner: rekursiv zählen
$dirs++;
$iterator = @scandir($path);
if ($iterator === false) {
return ['files' => $files, 'dirs' => $dirs, 'bytes' => $bytes];
}
foreach ($iterator as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
$childPath = $path . DIRECTORY_SEPARATOR . $entry;
if (is_dir($childPath)) {
$childStats = $this->calculateLocalUploadStats($childPath, ['isDir' => true]);
$files += $childStats['files'];
$dirs += $childStats['dirs'];
$bytes += $childStats['bytes'];
} elseif (is_file($childPath) && is_readable($childPath)) {
$files++;
$bytes += (int) @filesize($childPath);
}
}
return ['files' => $files, 'dirs' => $dirs, 'bytes' => $bytes];
}
/**
* Queue für Datei-Uploads aufbauen (Datei- oder Ordner-Auswahl).
*
* @return array<int,array{local:string,remote:string}>
*/
private function buildUploadQueue(string $localPath, array $row, string $remoteDir): array
{
$queue = [];
$remoteBase = rtrim($remoteDir, '/');
if ($remoteBase === '') {
$remoteBase = '/';
}
$isDir = (bool) ($row['isDir'] ?? false);
if (!$isDir) {
$queue[] = [
'local' => $localPath,
'remote' => $remoteBase . '/' . basename($localPath),
];
return $queue;
}
// Wurzelordner auf Remote
$rootRemote = $remoteBase . '/' . basename($localPath);
$this->collectUploadFilesRecursive($localPath, $rootRemote, $queue);
return $queue;
}
/**
* Rekursive Sammlung aller Dateien eines Ordners.
*
* @param array<int,array{local:string,remote:string}> $queue
*/
private function collectUploadFilesRecursive(string $localDir, string $remoteDir, array &$queue): void
{
$entries = @scandir($localDir);
if ($entries === false) {
return;
}
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
$localChild = $localDir . DIRECTORY_SEPARATOR . $entry;
$remoteChild = rtrim($remoteDir, '/') . '/' . $entry;
if (is_dir($localChild)) {
$this->collectUploadFilesRecursive($localChild, $remoteChild, $queue);
} elseif (is_file($localChild) && is_readable($localChild)) {
$queue[] = [
'local' => $localChild,
'remote' => $remoteChild,
];
}
}
}
/**
* Nächste Datei aus der Upload-Queue hochladen und ProgressBar aktualisieren.
*/
private function startNextUploadTask(
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
if ($this->totalUploadFiles <= 0) {
$this->transferProgressBar->setValue(1.0);
$this->transferInfoLabel->setText('Keine Dateien zum Hochladen');
return;
}
if (empty($this->pendingUploadQueue)) {
// Alle Dateien hochgeladen
$this->transferProgressBar->setValue(1.0);
$this->transferInfoLabel->setText('Upload abgeschlossen');
$selectedServerRef = &$serverListTab->selectedServer;
$this->reloadCurrentDirectory($currentPrivateKeyPath, $selectedServerRef, $statusLabel);
return;
}
$item = array_shift($this->pendingUploadQueue);
$localPath = $item['local'];
$remotePath = $item['remote'];
$selectedServerRef = &$serverListTab->selectedServer;
$sftpTab = $this;
$uploadAsyncButton = new Button('UploadChunk', '');
$uploadAsyncButton->setOnClickAsync(
function () use (&$currentPrivateKeyPath, &$selectedServerRef, $localPath, $remotePath) {
if ($selectedServerRef === null || empty($currentPrivateKeyPath)) {
return ['error' => 'Not connected'];
}
$selectedServer = $selectedServerRef;
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'];
}
// Zielverzeichnis sicherstellen
$remoteDir = dirname($remotePath);
if (!$sftp->is_dir($remoteDir)) {
if (!$sftp->mkdir($remoteDir, -1, true)) {
return ['error' => 'Kann Remote-Verzeichnis nicht anlegen'];
}
}
if (!is_file($localPath) || !is_readable($localPath)) {
return ['error' => 'Lokale Datei ist nicht lesbar'];
}
$result = $sftp->put(
$remotePath,
$localPath,
\phpseclib3\Net\SFTP::SOURCE_LOCAL_FILE,
);
if ($result === false) {
return ['error' => 'Upload fehlgeschlagen'];
}
return ['success' => true];
} catch (\Exception $e) {
return ['error' => $e->getMessage()];
}
},
function ($result) use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel) {
if (isset($result['error'])) {
$sftpTab->transferInfoLabel->setText('Upload fehlgeschlagen: ' . $result['error']);
$statusLabel->setText('Fehler beim Hochladen: ' . $result['error']);
return;
}
if (isset($result['success'])) {
$sftpTab->completedUploadFiles++;
$progress = $sftpTab->totalUploadFiles > 0
? ($sftpTab->completedUploadFiles / $sftpTab->totalUploadFiles)
: 1.0;
$sftpTab->transferProgressBar->setValue($progress);
$sftpTab->transferInfoLabel->setText(
sprintf(
'Upload: %d / %d Dateien',
$sftpTab->completedUploadFiles,
$sftpTab->totalUploadFiles,
),
);
// Nächste Datei starten
$sftpTab->startNextUploadTask($currentPrivateKeyPath, $serverListTab, $statusLabel);
}
},
function ($error) use ($sftpTab, $statusLabel) {
$errorMsg = is_string($error) ? $error : 'Unknown error';
$sftpTab->transferInfoLabel->setText('Upload fehlgeschlagen: ' . $errorMsg);
$statusLabel->setText('Fehler beim Hochladen: ' . $errorMsg);
},
);
$uploadAsyncButton->handleMouseClick(0, 0, 0);
}
/**
* Nächste Datei aus der Download-Queue holen und Fortschritt aktualisieren.
*/
private function startNextDownloadTask(
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
string $localRootDir,
): void {
if ($this->totalDownloadFiles <= 0) {
$this->transferProgressBar->setValue(1.0);
$this->transferInfoLabel->setText('Keine Dateien zum Herunterladen');
return;
}
if (empty($this->pendingDownloadQueue)) {
// Alle Dateien heruntergeladen
$this->transferProgressBar->setValue(1.0);
$this->transferInfoLabel->setText('Download abgeschlossen');
$this->localFileBrowser->loadDirectory($localRootDir);
return;
}
$item = array_shift($this->pendingDownloadQueue);
$remotePath = $item['remote'];
$localPath = $item['local'];
$selectedServerRef = &$serverListTab->selectedServer;
$sftpTab = $this;
$downloadAsyncButton = new Button('DownloadChunk', '');
$downloadAsyncButton->setOnClickAsync(
function () use (&$currentPrivateKeyPath, &$selectedServerRef, $remotePath, $localPath) {
if ($selectedServerRef === null || empty($currentPrivateKeyPath)) {
return ['error' => 'Not connected'];
}
$selectedServer = $selectedServerRef;
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'];
}
$localDir = dirname($localPath);
if (!is_dir($localDir)) {
if (!mkdir($localDir, 0777, true) && !is_dir($localDir)) {
return ['error' => 'Lokales Zielverzeichnis kann nicht erstellt werden'];
}
}
$result = $sftp->get($remotePath, $localPath);
if ($result === false) {
return ['error' => 'Download fehlgeschlagen'];
}
return ['success' => true];
} catch (\Exception $e) {
return ['error' => $e->getMessage()];
}
},
function ($result) use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel, $localRootDir) {
if (isset($result['error'])) {
$sftpTab->transferInfoLabel->setText('Download fehlgeschlagen: ' . $result['error']);
$statusLabel->setText('Fehler beim Herunterladen: ' . $result['error']);
return;
}
if (isset($result['success'])) {
$sftpTab->completedDownloadFiles++;
$progress = $sftpTab->totalDownloadFiles > 0
? ($sftpTab->completedDownloadFiles / $sftpTab->totalDownloadFiles)
: 1.0;
$sftpTab->transferProgressBar->setValue($progress);
$sftpTab->transferInfoLabel->setText(
sprintf(
'Download: %d / %d Dateien',
$sftpTab->completedDownloadFiles,
$sftpTab->totalDownloadFiles,
),
);
// Nächste Datei herunterladen
$sftpTab->startNextDownloadTask($currentPrivateKeyPath, $serverListTab, $statusLabel, $localRootDir);
}
},
function ($error) use ($sftpTab, $statusLabel) {
$errorMsg = is_string($error) ? $error : 'Unknown error';
$sftpTab->transferInfoLabel->setText('Download fehlgeschlagen: ' . $errorMsg);
$statusLabel->setText('Fehler beim Herunterladen: ' . $errorMsg);
},
);
$downloadAsyncButton->handleMouseClick(0, 0, 0);
}
private function handleDownload(
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
if ($this->currentRemoteSelection === null) {
$statusLabel->setText('Keine Remote-Datei ausgewählt');
return;
}
$remoteRow = $this->currentRemoteSelection['row'] ?? null;
$remotePath = $this->currentRemoteSelection['path'] ?? null;
if ($remoteRow === null || $remotePath === null) {
$statusLabel->setText('Ungültige Remote-Auswahl');
return;
}
if ($serverListTab->selectedServer === null) {
$statusLabel->setText('Kein Server ausgewählt');
return;
}
if (empty($currentPrivateKeyPath) || !file_exists($currentPrivateKeyPath)) {
$statusLabel->setText('Private Key Pfad nicht konfiguriert oder Datei nicht gefunden');
return;
}
$localDir = $this->localFileBrowser->getCurrentPath();
if ($localDir === '') {
$localDir = getcwd();
}
if (!is_dir($localDir) || !is_writable($localDir)) {
$statusLabel->setText('Lokales Verzeichnis ist nicht beschreibbar');
return;
}
$this->transferProgressBar->setValue(0.0);
$this->transferInfoLabel->setText('Download wird vorbereitet …');
// Async-Scan der Remote-Struktur, um eine Download-Queue aufzubauen
$selectedServerRef = &$serverListTab->selectedServer;
$sftpTab = $this;
$scanButton = new Button('ScanDownload', '');
$scanButton->setOnClickAsync(
function () use (&$currentPrivateKeyPath, &$selectedServerRef, $remotePath, $remoteRow, $localDir) {
if ($selectedServerRef === null || empty($currentPrivateKeyPath)) {
return ['error' => 'Not connected'];
}
$selectedServer = $selectedServerRef;
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'];
}
$queue = [];
$files = 0;
$dirs = 0;
$isDir = (bool) ($remoteRow['isDir'] ?? false);
if (!$isDir) {
$queue[] = [
'remote' => $remotePath,
'local' => rtrim($localDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . basename($remotePath),
];
$files = 1;
} else {
$rootLocal = rtrim($localDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . basename($remotePath);
$collect = null;
$collect = function (string $srcDir, string $dstDir) use (&$collect, $sftp, &$queue, &$files, &$dirs) {
$dirs++;
$entries = $sftp->nlist($srcDir);
if ($entries === false) {
return;
}
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
$remoteChild = rtrim($srcDir, '/') . '/' . $entry;
$localChild = rtrim($dstDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $entry;
$stat = $sftp->stat($remoteChild);
$isDirChild = ($stat['type'] ?? 0) === 2;
if ($isDirChild) {
$collect($remoteChild, $localChild);
} else {
$queue[] = [
'remote' => $remoteChild,
'local' => $localChild,
];
$files++;
}
}
};
$collect($remotePath, $rootLocal);
}
return [
'success' => true,
'queue' => $queue,
'files' => $files,
'dirs' => $dirs,
'localDir' => $localDir,
];
} catch (\Exception $e) {
return ['error' => $e->getMessage()];
}
},
function ($result) use ($sftpTab, $statusLabel, &$currentPrivateKeyPath, $serverListTab) {
if (isset($result['error'])) {
$statusLabel->setText('Fehler beim Vorbereiten des Downloads: ' . $result['error']);
$sftpTab->transferInfoLabel->setText('Download fehlgeschlagen');
return;
}
if (!isset($result['success']) || !$result['success']) {
$statusLabel->setText('Unbekannter Fehler beim Vorbereiten des Downloads');
$sftpTab->transferInfoLabel->setText('Download fehlgeschlagen');
return;
}
$queue = $result['queue'] ?? [];
$files = (int) ($result['files'] ?? 0);
$dirs = (int) ($result['dirs'] ?? 0);
$sftpTab->pendingDownloadQueue = $queue;
$sftpTab->totalDownloadFiles = count($queue);
$sftpTab->completedDownloadFiles = 0;
if ($sftpTab->totalDownloadFiles <= 0) {
$sftpTab->transferProgressBar->setValue(1.0);
$sftpTab->transferInfoLabel->setText('Keine Dateien zum Herunterladen');
return;
}
$sftpTab->transferProgressBar->setValue(0.0);
$sftpTab->transferInfoLabel->setText(
sprintf(
'Download: %d Dateien, %d Ordner',
$files,
$dirs,
),
);
$localDir = $result['localDir'] ?? '';
$sftpTab->startNextDownloadTask($currentPrivateKeyPath, $serverListTab, $statusLabel, $localDir);
},
function ($error) use ($sftpTab, $statusLabel) {
$errorMsg = is_string($error) ? $error : 'Unknown error';
$statusLabel->setText('Fehler beim Vorbereiten des Downloads: ' . $errorMsg);
$sftpTab->transferInfoLabel->setText('Download fehlgeschlagen');
},
);
$scanButton->handleMouseClick(0, 0, 0);
}
private function createRenameModal(
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
// Create modal content
$modalContent = new Container('flex flex-col bg-white rounded-lg shadow-lg p-4 gap-3 w-96');
// Title
$modalContent->addComponent(new Label('Datei umbenennen', 'text-lg font-bold text-black'));
// Filename input
$this->renameInput = new TextInput(
'Neuer Dateiname',
'w-full border-2 border-gray-300 rounded px-3 py-2 bg-white text-black',
);
$modalContent->addComponent($this->renameInput);
// Button container
$buttonContainer = new Container('flex flex-row justify-end gap-2');
// Cancel button
$cancelButton = new Button('Abbrechen', 'px-4 py-2 bg-gray-300 text-black rounded hover:bg-gray-400');
$sftpTab = $this;
$cancelButton->setOnClick(function () use ($sftpTab) {
$sftpTab->renameModal->setVisible(false);
});
$buttonContainer->addComponent($cancelButton);
// Rename button
$renameButton = new Button('Umbenennen', 'px-4 py-2 bg-amber-600 text-white rounded hover:bg-amber-700');
$renameButton->setOnClick(function () use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel) {
$newFilename = trim($sftpTab->renameInput->getValue());
if (empty($newFilename)) {
$statusLabel->setText('Dateiname darf nicht leer sein');
return;
}
$oldPath = $sftpTab->currentRenameFilePath;
$directory = dirname($oldPath);
$newPath = $directory . '/' . $newFilename;
// Close rename modal
$sftpTab->renameModal->setVisible(false);
// Perform rename via SFTP
$selectedServerRef = &$serverListTab->selectedServer;
$renameAsyncButton = new Button('Rename', '');
$renameAsyncButton->setOnClickAsync(
function () use ($oldPath, $newPath, &$currentPrivateKeyPath, &$selectedServerRef) {
if ($selectedServerRef === null || empty($currentPrivateKeyPath)) {
return ['error' => 'Not connected'];
}
$selectedServer = $selectedServerRef;
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'];
}
$result = $sftp->rename($oldPath, $newPath);
if ($result === false) {
return ['error' => 'Could not rename file'];
}
return ['success' => true];
} catch (\Exception $e) {
return ['error' => $e->getMessage()];
}
},
function ($result) use ($sftpTab, $statusLabel, &$currentPrivateKeyPath, &$selectedServerRef) {
if (isset($result['error'])) {
$statusLabel->setText('Fehler beim Umbenennen: ' . $result['error']);
return;
}
if (isset($result['success'])) {
$statusLabel->setText('Datei erfolgreich umbenannt');
$sftpTab->reloadCurrentDirectory($currentPrivateKeyPath, $selectedServerRef, $statusLabel);
}
},
function ($error) use ($statusLabel) {
$errorMsg = is_string($error) ? $error : 'Unknown error';
$statusLabel->setText('Fehler beim Umbenennen: ' . $errorMsg);
},
);
$renameAsyncButton->handleMouseClick(0, 0, 0);
});
$buttonContainer->addComponent($renameButton);
$modalContent->addComponent($buttonContainer);
// Create modal
$this->renameModal = new Modal($modalContent);
}
private function createDeleteConfirmModal(
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
// Create modal content
$modalContent = new Container('flex flex-col bg-white rounded-lg shadow-lg p-4 gap-3 w-96');
// Title
$modalContent->addComponent(new Label('Datei löschen', 'text-lg font-bold text-black'));
// Confirmation message
$this->deleteConfirmLabel = new Label(
'Möchten Sie diese Datei wirklich löschen?',
'text-sm text-gray-700',
);
$modalContent->addComponent($this->deleteConfirmLabel);
// Button container
$buttonContainer = new Container('flex flex-row justify-end gap-2');
// Cancel button
$cancelButton = new Button('Abbrechen', 'px-4 py-2 bg-gray-300 text-black rounded hover:bg-gray-400');
$sftpTab = $this;
$cancelButton->setOnClick(function () use ($sftpTab) {
$sftpTab->deleteConfirmModal->setVisible(false);
});
$buttonContainer->addComponent($cancelButton);
// Delete button
$deleteButton = new Button('Löschen', 'px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700');
$deleteButton->setOnClick(function () use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel) {
$filePath = $sftpTab->currentDeleteFilePath;
// Close delete modal
$sftpTab->deleteConfirmModal->setVisible(false);
// Perform delete via SFTP
$selectedServerRef = &$serverListTab->selectedServer;
$deleteAsyncButton = new Button('Delete', '');
$deleteAsyncButton->setOnClickAsync(
function () use ($filePath, &$currentPrivateKeyPath, &$selectedServerRef) {
if ($selectedServerRef === null || empty($currentPrivateKeyPath)) {
return ['error' => 'Not connected'];
}
$selectedServer = $selectedServerRef;
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'];
}
$result = $sftp->delete($filePath);
if ($result === false) {
return ['error' => 'Could not delete file'];
}
return ['success' => true];
} catch (\Exception $e) {
return ['error' => $e->getMessage()];
}
},
function ($result) use ($sftpTab, $statusLabel, &$currentPrivateKeyPath, &$selectedServerRef) {
if (isset($result['error'])) {
$statusLabel->setText('Fehler beim Löschen: ' . $result['error']);
return;
}
if (isset($result['success'])) {
$statusLabel->setText('Datei erfolgreich gelöscht');
$sftpTab->reloadCurrentDirectory($currentPrivateKeyPath, $selectedServerRef, $statusLabel);
}
},
function ($error) use ($statusLabel) {
$errorMsg = is_string($error) ? $error : 'Unknown error';
$statusLabel->setText('Fehler beim Löschen: ' . $errorMsg);
},
);
$deleteAsyncButton->handleMouseClick(0, 0, 0);
});
$buttonContainer->addComponent($deleteButton);
$modalContent->addComponent($buttonContainer);
// Create modal
$this->deleteConfirmModal = new Modal($modalContent);
}
private function handleFileRename(
string $path,
array $row,
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
if ($serverListTab->selectedServer === null) {
$statusLabel->setText('Kein Server ausgewählt');
return;
}
$this->currentRenameFilePath = $path;
$filename = basename($path);
$this->renameInput->setValue($filename);
$this->renameModal->setVisible(true);
}
private function handleFileDelete(
string $path,
array $row,
string &$currentPrivateKeyPath,
ServerListTab $serverListTab,
Label $statusLabel,
): void {
if ($serverListTab->selectedServer === null) {
$statusLabel->setText('Kein Server ausgewählt');
return;
}
$this->currentDeleteFilePath = $path;
$filename = basename($path);
$this->deleteConfirmLabel->setText('Möchten Sie die Datei "' . $filename . '" wirklich löschen?');
$this->deleteConfirmModal->setVisible(true);
}
}