This commit is contained in:
Thomas Peterson 2025-12-01 11:23:20 +01:00
parent d4a926ddad
commit b991570285
3 changed files with 101 additions and 46 deletions

View File

@ -36,12 +36,17 @@ class SftpManagerTab
private float $lastRemoteClickTime = 0.0; private float $lastRemoteClickTime = 0.0;
private ProgressBar $transferProgressBar; private ProgressBar $transferProgressBar;
private Label $transferInfoLabel; private Label $transferInfoLabel;
private Label $transferBytesLabel;
private array $pendingUploadQueue = []; private array $pendingUploadQueue = [];
private int $totalUploadFiles = 0; private int $totalUploadFiles = 0;
private int $completedUploadFiles = 0; private int $completedUploadFiles = 0;
private int $totalUploadBytes = 0;
private int $completedUploadBytes = 0;
private array $pendingDownloadQueue = []; private array $pendingDownloadQueue = [];
private int $totalDownloadFiles = 0; private int $totalDownloadFiles = 0;
private int $completedDownloadFiles = 0; private int $completedDownloadFiles = 0;
private int $totalDownloadBytes = 0;
private int $completedDownloadBytes = 0;
public function __construct( public function __construct(
string &$apiKey, string &$apiKey,
@ -112,9 +117,11 @@ class SftpManagerTab
// Transfer-Info + ProgressBar // Transfer-Info + ProgressBar
$this->transferInfoLabel = new Label('Kein Transfer aktiv', 'text-xs text-gray-600 mt-2'); $this->transferInfoLabel = new Label('Kein Transfer aktiv', 'text-xs text-gray-600 mt-2');
$this->transferBytesLabel = new Label('', 'text-xs text-gray-600');
$this->transferProgressBar = new ProgressBar('mt-1'); $this->transferProgressBar = new ProgressBar('mt-1');
$this->transferProgressBar->setValue(0.0); $this->transferProgressBar->setValue(0.0);
$transferContainer->addComponent($this->transferInfoLabel); $transferContainer->addComponent($this->transferInfoLabel);
$transferContainer->addComponent($this->transferBytesLabel);
$transferContainer->addComponent($this->transferProgressBar); $transferContainer->addComponent($this->transferProgressBar);
$this->tab->addComponent($localBrowserContainer); $this->tab->addComponent($localBrowserContainer);
@ -807,26 +814,30 @@ class SftpManagerTab
$remoteDir = '/'; $remoteDir = '/';
} }
// Upload-Größe vorab bestimmen // Upload-Queue inkl. Byte-Größen vorbereiten
$stats = $this->calculateLocalUploadStats($localPath, $localRow); $this->pendingUploadQueue = $this->buildUploadQueue($localPath, $localRow, $remoteDir);
$totalBytes = $stats['bytes']; $this->totalUploadFiles = count($this->pendingUploadQueue);
$fileCount = $stats['files']; $this->completedUploadFiles = 0;
$dirCount = $stats['dirs']; $this->totalUploadBytes = 0;
$this->completedUploadBytes = 0;
foreach ($this->pendingUploadQueue as $item) {
$this->totalUploadBytes += (int) ($item['size'] ?? 0);
}
$this->transferProgressBar->setValue(0.0); $this->transferProgressBar->setValue(0.0);
$this->transferInfoLabel->setText( $this->transferInfoLabel->setText(
sprintf( sprintf(
'Upload: %d Dateien, %d Ordner (%.2f MB)', 'Upload: %d Dateien',
$fileCount, $this->totalUploadFiles,
$dirCount, ),
$totalBytes > 0 ? ($totalBytes / (1024 * 1024)) : 0, );
$this->transferBytesLabel->setText(
sprintf(
'0.00 / %.2f MB',
$this->totalUploadBytes > 0 ? ($this->totalUploadBytes / (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) { if ($this->totalUploadFiles <= 0) {
$this->transferProgressBar->setValue(1.0); $this->transferProgressBar->setValue(1.0);
@ -904,6 +915,7 @@ class SftpManagerTab
$queue[] = [ $queue[] = [
'local' => $localPath, 'local' => $localPath,
'remote' => $remoteBase . '/' . basename($localPath), 'remote' => $remoteBase . '/' . basename($localPath),
'size' => is_file($localPath) ? (int) @filesize($localPath) : 0,
]; ];
return $queue; return $queue;
} }
@ -941,6 +953,7 @@ class SftpManagerTab
$queue[] = [ $queue[] = [
'local' => $localChild, 'local' => $localChild,
'remote' => $remoteChild, 'remote' => $remoteChild,
'size' => is_file($localChild) ? (int) @filesize($localChild) : 0,
]; ];
} }
} }
@ -973,6 +986,7 @@ class SftpManagerTab
$item = array_shift($this->pendingUploadQueue); $item = array_shift($this->pendingUploadQueue);
$localPath = $item['local']; $localPath = $item['local'];
$remotePath = $item['remote']; $remotePath = $item['remote'];
$fileSize = (int) ($item['size'] ?? 0);
$selectedServerRef = &$serverListTab->selectedServer; $selectedServerRef = &$serverListTab->selectedServer;
$sftpTab = $this; $sftpTab = $this;
@ -1020,7 +1034,7 @@ class SftpManagerTab
return ['error' => $e->getMessage()]; return ['error' => $e->getMessage()];
} }
}, },
function ($result) use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel) { function ($result) use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel, $fileSize) {
if (isset($result['error'])) { if (isset($result['error'])) {
$sftpTab->transferInfoLabel->setText('Upload fehlgeschlagen: ' . $result['error']); $sftpTab->transferInfoLabel->setText('Upload fehlgeschlagen: ' . $result['error']);
$statusLabel->setText('Fehler beim Hochladen: ' . $result['error']); $statusLabel->setText('Fehler beim Hochladen: ' . $result['error']);
@ -1029,9 +1043,17 @@ class SftpManagerTab
if (isset($result['success'])) { if (isset($result['success'])) {
$sftpTab->completedUploadFiles++; $sftpTab->completedUploadFiles++;
$progress = $sftpTab->totalUploadFiles > 0 $sftpTab->completedUploadBytes += $fileSize;
? ($sftpTab->completedUploadFiles / $sftpTab->totalUploadFiles)
: 1.0; $progress = 0.0;
if ($sftpTab->totalUploadBytes > 0) {
$progress = $sftpTab->completedUploadBytes / $sftpTab->totalUploadBytes;
} elseif ($sftpTab->totalUploadFiles > 0) {
$progress = $sftpTab->completedUploadFiles / $sftpTab->totalUploadFiles;
} else {
$progress = 1.0;
}
$sftpTab->transferProgressBar->setValue($progress); $sftpTab->transferProgressBar->setValue($progress);
$sftpTab->transferInfoLabel->setText( $sftpTab->transferInfoLabel->setText(
sprintf( sprintf(
@ -1040,6 +1062,17 @@ class SftpManagerTab
$sftpTab->totalUploadFiles, $sftpTab->totalUploadFiles,
), ),
); );
$sftpTab->transferBytesLabel->setText(
sprintf(
'%.2f / %.2f MB',
$sftpTab->completedUploadBytes > 0
? ($sftpTab->completedUploadBytes / (1024 * 1024))
: 0,
$sftpTab->totalUploadBytes > 0
? ($sftpTab->totalUploadBytes / (1024 * 1024))
: 0,
),
);
// Nächste Datei starten // Nächste Datei starten
$sftpTab->startNextUploadTask($currentPrivateKeyPath, $serverListTab, $statusLabel); $sftpTab->startNextUploadTask($currentPrivateKeyPath, $serverListTab, $statusLabel);
@ -1081,6 +1114,7 @@ class SftpManagerTab
$item = array_shift($this->pendingDownloadQueue); $item = array_shift($this->pendingDownloadQueue);
$remotePath = $item['remote']; $remotePath = $item['remote'];
$localPath = $item['local']; $localPath = $item['local'];
$fileSize = (int) ($item['size'] ?? 0);
$selectedServerRef = &$serverListTab->selectedServer; $selectedServerRef = &$serverListTab->selectedServer;
$sftpTab = $this; $sftpTab = $this;
@ -1119,7 +1153,7 @@ class SftpManagerTab
return ['error' => $e->getMessage()]; return ['error' => $e->getMessage()];
} }
}, },
function ($result) use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel, $localRootDir) { function ($result) use ($sftpTab, &$currentPrivateKeyPath, $serverListTab, $statusLabel, $localRootDir, $fileSize) {
if (isset($result['error'])) { if (isset($result['error'])) {
$sftpTab->transferInfoLabel->setText('Download fehlgeschlagen: ' . $result['error']); $sftpTab->transferInfoLabel->setText('Download fehlgeschlagen: ' . $result['error']);
$statusLabel->setText('Fehler beim Herunterladen: ' . $result['error']); $statusLabel->setText('Fehler beim Herunterladen: ' . $result['error']);
@ -1128,9 +1162,17 @@ class SftpManagerTab
if (isset($result['success'])) { if (isset($result['success'])) {
$sftpTab->completedDownloadFiles++; $sftpTab->completedDownloadFiles++;
$progress = $sftpTab->totalDownloadFiles > 0 $sftpTab->completedDownloadBytes += $fileSize;
? ($sftpTab->completedDownloadFiles / $sftpTab->totalDownloadFiles)
: 1.0; $progress = 0.0;
if ($sftpTab->totalDownloadBytes > 0) {
$progress = $sftpTab->completedDownloadBytes / $sftpTab->totalDownloadBytes;
} elseif ($sftpTab->totalDownloadFiles > 0) {
$progress = $sftpTab->completedDownloadFiles / $sftpTab->totalDownloadFiles;
} else {
$progress = 1.0;
}
$sftpTab->transferProgressBar->setValue($progress); $sftpTab->transferProgressBar->setValue($progress);
$sftpTab->transferInfoLabel->setText( $sftpTab->transferInfoLabel->setText(
sprintf( sprintf(
@ -1139,6 +1181,17 @@ class SftpManagerTab
$sftpTab->totalDownloadFiles, $sftpTab->totalDownloadFiles,
), ),
); );
$sftpTab->transferBytesLabel->setText(
sprintf(
'%.2f / %.2f MB',
$sftpTab->completedDownloadBytes > 0
? ($sftpTab->completedDownloadBytes / (1024 * 1024))
: 0,
$sftpTab->totalDownloadBytes > 0
? ($sftpTab->totalDownloadBytes / (1024 * 1024))
: 0,
),
);
// Nächste Datei herunterladen // Nächste Datei herunterladen
$sftpTab->startNextDownloadTask($currentPrivateKeyPath, $serverListTab, $statusLabel, $localRootDir); $sftpTab->startNextDownloadTask($currentPrivateKeyPath, $serverListTab, $statusLabel, $localRootDir);
@ -1219,19 +1272,24 @@ class SftpManagerTab
$queue = []; $queue = [];
$files = 0; $files = 0;
$dirs = 0; $dirs = 0;
$totalBytes = 0;
$isDir = (bool) ($remoteRow['isDir'] ?? false); $isDir = (bool) ($remoteRow['isDir'] ?? false);
if (!$isDir) { if (!$isDir) {
$stat = $sftp->stat($remotePath);
$size = (int) ($stat['size'] ?? 0);
$queue[] = [ $queue[] = [
'remote' => $remotePath, 'remote' => $remotePath,
'local' => rtrim($localDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . basename($remotePath), 'local' => rtrim($localDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . basename($remotePath),
'size' => $size,
]; ];
$files = 1; $files = 1;
$totalBytes = $size;
} else { } else {
$rootLocal = rtrim($localDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . basename($remotePath); $rootLocal = rtrim($localDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . basename($remotePath);
$collect = null; $collect = null;
$collect = function (string $srcDir, string $dstDir) use (&$collect, $sftp, &$queue, &$files, &$dirs) { $collect = function (string $srcDir, string $dstDir) use (&$collect, $sftp, &$queue, &$files, &$dirs, &$totalBytes) {
$dirs++; $dirs++;
$entries = $sftp->nlist($srcDir); $entries = $sftp->nlist($srcDir);
@ -1253,11 +1311,14 @@ class SftpManagerTab
if ($isDirChild) { if ($isDirChild) {
$collect($remoteChild, $localChild); $collect($remoteChild, $localChild);
} else { } else {
$size = (int) ($stat['size'] ?? 0);
$queue[] = [ $queue[] = [
'remote' => $remoteChild, 'remote' => $remoteChild,
'local' => $localChild, 'local' => $localChild,
'size' => $size,
]; ];
$files++; $files++;
$totalBytes += $size;
} }
} }
}; };
@ -1271,6 +1332,7 @@ class SftpManagerTab
'files' => $files, 'files' => $files,
'dirs' => $dirs, 'dirs' => $dirs,
'localDir' => $localDir, 'localDir' => $localDir,
'totalBytes' => $totalBytes,
]; ];
} catch (\Exception $e) { } catch (\Exception $e) {
return ['error' => $e->getMessage()]; return ['error' => $e->getMessage()];
@ -1292,14 +1354,18 @@ class SftpManagerTab
$queue = $result['queue'] ?? []; $queue = $result['queue'] ?? [];
$files = (int) ($result['files'] ?? 0); $files = (int) ($result['files'] ?? 0);
$dirs = (int) ($result['dirs'] ?? 0); $dirs = (int) ($result['dirs'] ?? 0);
$totalBytes = (int) ($result['totalBytes'] ?? 0);
$sftpTab->pendingDownloadQueue = $queue; $sftpTab->pendingDownloadQueue = $queue;
$sftpTab->totalDownloadFiles = count($queue); $sftpTab->totalDownloadFiles = count($queue);
$sftpTab->completedDownloadFiles = 0; $sftpTab->completedDownloadFiles = 0;
$sftpTab->totalDownloadBytes = $totalBytes;
$sftpTab->completedDownloadBytes = 0;
if ($sftpTab->totalDownloadFiles <= 0) { if ($sftpTab->totalDownloadFiles <= 0) {
$sftpTab->transferProgressBar->setValue(1.0); $sftpTab->transferProgressBar->setValue(1.0);
$sftpTab->transferInfoLabel->setText('Keine Dateien zum Herunterladen'); $sftpTab->transferInfoLabel->setText('Keine Dateien zum Herunterladen');
$sftpTab->transferBytesLabel->setText('');
return; return;
} }
@ -1311,6 +1377,12 @@ class SftpManagerTab
$dirs, $dirs,
), ),
); );
$sftpTab->transferBytesLabel->setText(
sprintf(
'0.00 / %.2f MB',
$totalBytes > 0 ? ($totalBytes / (1024 * 1024)) : 0,
),
);
$localDir = $result['localDir'] ?? ''; $localDir = $result['localDir'] ?? '';
$sftpTab->startNextDownloadTask($currentPrivateKeyPath, $serverListTab, $statusLabel, $localDir); $sftpTab->startNextDownloadTask($currentPrivateKeyPath, $serverListTab, $statusLabel, $localDir);

View File

@ -13,7 +13,7 @@ class FileBrowser extends Container
private $onRenameFile = null; private $onRenameFile = null;
private $onDeleteFile = null; private $onDeleteFile = null;
private bool $isRemote = false; private bool $isRemote = false;
private ?string $lastClickPath = null; private null|string $lastClickPath = null;
private float $lastClickTime = 0.0; private float $lastClickTime = 0.0;
public function __construct(string $initialPath = '.', bool $isRemote = false, string $style = '') public function __construct(string $initialPath = '.', bool $isRemote = false, string $style = '')
@ -136,10 +136,7 @@ class FileBrowser extends Container
$now = microtime(true); $now = microtime(true);
$doubleClickThreshold = 0.4; // Sekunden $doubleClickThreshold = 0.4; // Sekunden
if ( if ($fileBrowser->lastClickPath !== $path || ($now - $fileBrowser->lastClickTime) > $doubleClickThreshold) {
$fileBrowser->lastClickPath !== $path ||
($now - $fileBrowser->lastClickTime) > $doubleClickThreshold
) {
// Erster Klick: nur merken // Erster Klick: nur merken
$fileBrowser->lastClickPath = $path; $fileBrowser->lastClickPath = $path;
$fileBrowser->lastClickTime = $now; $fileBrowser->lastClickTime = $now;
@ -288,16 +285,14 @@ class FileBrowser extends Container
public function renderActionsCell(array $rowData, int $rowIndex): Container public function renderActionsCell(array $rowData, int $rowIndex): Container
{ {
// Match the cell style from Table (100px width for icon buttons) // Match the cell style from Table (100px width for icon buttons)
$container = new Container( $container = new Container('w-25 border-r border-gray-300 flex flex-row items-center justify-center gap-1');
'w-25 py-1 border-r border-gray-300 flex flex-row items-center justify-center gap-1',
);
// Only show action buttons for files (not directories) // Only show action buttons for files (not directories)
if (!($rowData['isDir'] ?? false) && !empty($rowData['path'])) { if (!($rowData['isDir'] ?? false) && !empty($rowData['path'])) {
$fileBrowser = $this; $fileBrowser = $this;
// Edit button // Edit button
$editButton = new Button('', 'p-1 text-blue-500 hover:text-blue-600 flex items-center justify-center'); $editButton = new Button('', 'text-blue-500 hover:text-blue-600 flex items-center justify-center');
$editIcon = new Icon(\PHPNative\Tailwind\Data\Icon::edit, 16, 'text-blue-500'); $editIcon = new Icon(\PHPNative\Tailwind\Data\Icon::edit, 16, 'text-blue-500');
$editButton->setIcon($editIcon); $editButton->setIcon($editIcon);
$editButton->setOnClick(function () use ($fileBrowser, $rowData) { $editButton->setOnClick(function () use ($fileBrowser, $rowData) {
@ -308,7 +303,7 @@ class FileBrowser extends Container
$container->addComponent($editButton); $container->addComponent($editButton);
// Rename button // Rename button
$renameButton = new Button('', 'p-1 text-amber-500 hover:text-amber-600 flex items-center justify-center'); $renameButton = new Button('', 'text-amber-500 hover:text-amber-600 flex items-center justify-center');
$renameIcon = new Icon(\PHPNative\Tailwind\Data\Icon::pen, 16, 'text-amber-500'); $renameIcon = new Icon(\PHPNative\Tailwind\Data\Icon::pen, 16, 'text-amber-500');
$renameButton->setIcon($renameIcon); $renameButton->setIcon($renameIcon);
$renameButton->setOnClick(function () use ($fileBrowser, $rowData) { $renameButton->setOnClick(function () use ($fileBrowser, $rowData) {
@ -319,7 +314,7 @@ class FileBrowser extends Container
$container->addComponent($renameButton); $container->addComponent($renameButton);
// Delete button // Delete button
$deleteButton = new Button('', 'p-1 text-red-500 hover:text-red-600 flex items-center justify-center'); $deleteButton = new Button('', 'text-red-500 hover:text-red-600 flex items-center justify-center');
$deleteIcon = new Icon(\PHPNative\Tailwind\Data\Icon::trash, 16, 'text-red-500'); $deleteIcon = new Icon(\PHPNative\Tailwind\Data\Icon::trash, 16, 'text-red-500');
$deleteButton->setIcon($deleteIcon); $deleteButton->setIcon($deleteIcon);
$deleteButton->setOnClick(function () use ($fileBrowser, $rowData) { $deleteButton->setOnClick(function () use ($fileBrowser, $rowData) {

View File

@ -91,18 +91,6 @@ class Table extends Container
if ($preserveScroll && $scrollPosition !== null) { if ($preserveScroll && $scrollPosition !== null) {
$this->bodyContainer->setScrollPosition($scrollPosition['x'], $scrollPosition['y']); $this->bodyContainer->setScrollPosition($scrollPosition['x'], $scrollPosition['y']);
} }
// Debug-Ausgabe: Größe des Tabellen-Body und Viewport
$bodySize = $this->bodyContainer->getContentSize();
$bodyViewport = $this->bodyContainer->getContentViewport();
error_log(
sprintf(
'Table body debug: rows=%d, contentHeight=%d, viewportHeight=%d',
count($data),
(int) ($bodySize['height'] ?? 0),
(int) $bodyViewport->height,
),
);
} }
/** /**