926 lines
36 KiB
PHP
926 lines
36 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;
|
|
|
|
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;
|
|
|
|
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
|
|
$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);
|
|
|
|
// Right side: Remote file browser
|
|
$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);
|
|
|
|
$this->tab->addComponent($localBrowserContainer);
|
|
$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 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,
|
|
) {
|
|
if (!isset($row['isDir']) || !$row['isDir']) {
|
|
return;
|
|
}
|
|
|
|
$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
|
|
$tabContainer->setActiveTab(1);
|
|
|
|
$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 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);
|
|
}
|
|
}
|
|
|