*/ public array $entries = []; public function mount(): void { // listing loads lazily via wire:init -> load() } /** Read the active server's directory over SSH; empty + offline on failure. */ public function load(): void { $this->entries = []; $this->connected = false; $active = $this->activeServer(); if ($active && $active->credential_exists) { try { $this->entries = app(FleetService::class)->files($active, $this->path); $this->connected = true; } catch (Throwable) { $this->connected = false; } } $this->ready = true; } public function open(int $index): void { $name = $this->entries[$index]['name'] ?? null; if ($name === null) { return; } $this->path = rtrim($this->path, '/').'/'.$name; $this->load(); } public function go(int $index): void { $path = $this->crumbs[$index]['path'] ?? '/'; $this->path = $path !== '' ? $path : '/'; $this->load(); } public function up(): void { $this->path = dirname($this->path) ?: '/'; $this->load(); } /** Upload the selected file into the current directory (SFTP put). */ public function updatedUpload(FleetService $fleet): void { $this->validate(['upload' => ['file', 'max:51200']]); // 50 MB $active = $this->activeServer(); if ($active && $active->credential_exists && $this->upload) { try { $name = basename($this->upload->getClientOriginalName()); $fleet->uploadFile($active, rtrim($this->path, '/').'/'.$name, $this->upload->getRealPath()); $this->dispatch('notify', message: "„{$name}“ hochgeladen."); } catch (Throwable $e) { $this->dispatch('notify', message: 'Upload fehlgeschlagen: '.$e->getMessage()); } } $this->reset('upload'); $this->load(); } /** Stream a remote file to the browser as a download (SFTP get). */ public function download(int $index, FleetService $fleet) { $name = $this->entries[$index]['name'] ?? null; $active = $this->activeServer(); if ($name === null || ! $active || ! $active->credential_exists) { return null; } try { $content = $fleet->getFile($active, rtrim($this->path, '/').'/'.basename($name)); } catch (Throwable $e) { $this->dispatch('notify', message: $e->getMessage()); return null; } return response()->streamDownload(fn () => print ($content), basename($name)); } /** Open the view/edit modal for a file. */ public function edit(int $index): void { $name = $this->entries[$index]['name'] ?? null; $active = $this->activeServer(); if ($name === null || ! $active) { return; } $this->dispatch('openModal', component: 'modals.file-editor', arguments: [ 'serverId' => $active->id, 'path' => rtrim($this->path, '/').'/'.basename($name), 'name' => basename($name), ], ); } #[On('fileSaved')] public function reloadAfterSave(): void { $this->load(); } /** * Breadcrumb segments derived from $path (root first, then each folder). * * @return array */ public function getCrumbsProperty(): array { $crumbs = [['label' => '/', 'path' => '/']]; $acc = ''; foreach (array_filter(explode('/', $this->path)) as $segment) { $acc .= '/'.$segment; $crumbs[] = ['label' => $segment, 'path' => $acc]; } return $crumbs; } /** * Delete a file (R5): opens the confirm modal, which writes the AuditEvent * and re-dispatches `fileConfirmed` — handled by deleteEntry(), which performs * the SFTP unlink over the active server's connection. */ public function confirmDelete(int $index): void { $name = $this->entries[$index]['name'] ?? null; if ($name === null) { return; } $this->dispatch('openModal', component: 'modals.confirm-action', arguments: [ 'heading' => 'Datei löschen', 'body' => "„{$name}“ wird unwiderruflich aus {$this->path} entfernt.", 'confirmLabel' => 'Löschen', 'danger' => true, 'icon' => 'trash', 'auditAction' => 'file.delete', 'auditTarget' => rtrim($this->path, '/')."/{$name}", 'event' => 'fileConfirmed', 'params' => ['name' => $name], 'notify' => "„{$name}“ gelöscht.", ], ); } /** Applies the confirmed deletion over SFTP, then reloads the directory. */ #[On('fileConfirmed')] public function deleteEntry(string $name, FleetService $fleet): void { $active = $this->activeServer(); if ($active && $active->credential_exists) { try { $fleet->deleteFile($active, rtrim($this->path, '/').'/'.$name); } catch (Throwable $e) { $this->dispatch('notify', message: 'Löschen fehlgeschlagen: '.$e->getMessage()); return; } } $this->load(); } public function render() { return view('livewire.files.index'); } }