Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
40c7e2e
feat: add S3 storage integration for file import
andrasbacsai Nov 2, 2025
717c3c2
fix: S3 restore button disabled state and security scopes
andrasbacsai Nov 2, 2025
f2f2ac3
fix: S3 download and database restore output showing same content
andrasbacsai Nov 2, 2025
30f0340
Revert "fix: S3 download and database restore output showing same con…
andrasbacsai Nov 2, 2025
6a7e0eb
fix: conditionally render activity monitors to prevent output conflicts
andrasbacsai Nov 2, 2025
7791c6f
fix: ensure S3 download message hides when download finishes
andrasbacsai Nov 2, 2025
441cef5
fix: use x-show for activity monitors to enable reactive visibility
andrasbacsai Nov 2, 2025
ab28c33
fix: add updatedActivityId watcher to ActivityMonitor component
andrasbacsai Nov 2, 2025
f932488
fix: revert to original dispatch approach with unique wire:key per mo…
andrasbacsai Nov 2, 2025
b9e25c2
fix: use x-show for S3 download message to hide reactively on completion
andrasbacsai Nov 2, 2025
60bd6d7
debug: add ray logging to trace S3DownloadFinished event flow
andrasbacsai Nov 2, 2025
05632be
fix: add missing formatBytes helper function
andrasbacsai Nov 2, 2025
ec24bc2
fix: create S3 event classes and add formatBytes helper
andrasbacsai Nov 2, 2025
6d0cddd
fix: correct event class names in callEventOnFinish
andrasbacsai Nov 2, 2025
a104a1d
fix: broadcast S3DownloadFinished event to hide download message
andrasbacsai Nov 2, 2025
37f8f9c
fix: broadcast S3DownloadFinished to correct user
andrasbacsai Nov 2, 2025
754cf66
fix: only set s3DownloadedFile when download actually completes
andrasbacsai Nov 2, 2025
46be3fe
fix: remove blocking instant_remote_process and hide button during do…
andrasbacsai Nov 2, 2025
fe26786
fix: use server-side @if instead of client-side x-show for activity m…
andrasbacsai Nov 2, 2025
d9f89b8
fix: streamline helper version retrieval and improve migration clarity
andrasbacsai Nov 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions app/Events/S3DownloadFinished.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace App\Events;

use App\Models\Server;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class S3DownloadFinished implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;

public int|string|null $userId = null;

public ?string $downloadPath = null;

public function __construct($teamId, $data = null)
{
if (is_null($data)) {
return;
}

// Get userId from event data (the user who triggered the download)
$this->userId = data_get($data, 'userId');
$this->downloadPath = data_get($data, 'downloadPath');

$containerName = data_get($data, 'containerName');
$serverId = data_get($data, 'serverId');

if (filled($containerName) && filled($serverId)) {
// Clean up the MinIO client container
$commands = [];
$commands[] = "docker stop {$containerName} 2>/dev/null || true";
$commands[] = "docker rm {$containerName} 2>/dev/null || true";
instant_remote_process($commands, Server::find($serverId), throwError: false);
}
}

public function broadcastOn(): ?array
{
if (is_null($this->userId)) {
return [];
}

return [
new PrivateChannel("user.{$this->userId}"),
];
}

public function broadcastWith(): array
{
return [
'downloadPath' => $this->downloadPath,
];
}
}
49 changes: 49 additions & 0 deletions app/Events/S3RestoreJobFinished.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace App\Events;

use App\Models\Server;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class S3RestoreJobFinished
{
use Dispatchable, InteractsWithSockets, SerializesModels;

public function __construct($data)
{
$scriptPath = data_get($data, 'scriptPath');
$tmpPath = data_get($data, 'tmpPath');
$container = data_get($data, 'container');
$serverId = data_get($data, 'serverId');
$s3DownloadedFile = data_get($data, 's3DownloadedFile');

// Clean up temporary files from container
if (filled($scriptPath) && filled($tmpPath) && filled($container) && filled($serverId)) {
if (str($tmpPath)->startsWith('/tmp/')
&& str($scriptPath)->startsWith('/tmp/')
&& ! str($tmpPath)->contains('..')
&& ! str($scriptPath)->contains('..')
&& strlen($tmpPath) > 5 // longer than just "/tmp/"
&& strlen($scriptPath) > 5
) {
$commands[] = "docker exec {$container} sh -c 'rm {$scriptPath}'";
$commands[] = "docker exec {$container} sh -c 'rm {$tmpPath}'";
instant_remote_process($commands, Server::find($serverId), throwError: true);
}
}

// Clean up S3 downloaded file from server
if (filled($s3DownloadedFile) && filled($serverId)) {
if (str($s3DownloadedFile)->startsWith('/tmp/s3-restore-')
&& ! str($s3DownloadedFile)->contains('..')
&& strlen($s3DownloadedFile) > 16 // longer than just "/tmp/s3-restore-"
) {
$commands = [];
$commands[] = "rm -f {$s3DownloadedFile}";
instant_remote_process($commands, Server::find($serverId), throwError: false);
}
}
}
}
Loading