diff --git a/app/Livewire/Destination/Index.php b/app/Livewire/Destination/Index.php
index a3df3fd56..7a4b89fab 100644
--- a/app/Livewire/Destination/Index.php
+++ b/app/Livewire/Destination/Index.php
@@ -3,6 +3,7 @@
namespace App\Livewire\Destination;
use App\Models\Server;
+use Illuminate\Support\Collection;
use Livewire\Attributes\Locked;
use Livewire\Component;
@@ -11,9 +12,15 @@ class Index extends Component
#[Locked]
public $servers;
- public function mount()
+ #[Locked]
+ public Collection $destinations;
+
+ public function mount(): void
{
$this->servers = Server::isUsable()->get();
+ $this->destinations = $this->servers
+ ->flatMap(fn (Server $server) => $server->standaloneDockers->concat($server->swarmDockers))
+ ->values();
}
public function render()
diff --git a/app/Livewire/Destination/New/Docker.php b/app/Livewire/Destination/New/Docker.php
index 6f9b6f995..254823163 100644
--- a/app/Livewire/Destination/New/Docker.php
+++ b/app/Livewire/Destination/New/Docker.php
@@ -33,44 +33,49 @@ class Docker extends Component
#[Validate(['required', 'boolean'])]
public bool $isSwarm = false;
- public function mount(?string $server_id = null)
+ public function mount(?string $server_id = null): void
{
- $this->network = new Cuid2;
+ $this->network = (string) new Cuid2;
$this->servers = Server::isUsable()->get();
- if ($server_id) {
- $foundServer = $this->servers->find($server_id) ?: $this->servers->first();
- if (! $foundServer) {
- throw new \Exception('Server not found.');
+
+ if (filled($server_id)) {
+ $this->selectedServer = Server::ownedByCurrentTeam()->whereKey($server_id)->firstOrFail();
+
+ if (! $this->servers->contains('id', $this->selectedServer->id)) {
+ $this->servers->push($this->selectedServer);
}
- $this->selectedServer = $foundServer;
- $this->serverId = $this->selectedServer->id;
+
+ $this->serverId = (string) $this->selectedServer->id;
} else {
$foundServer = $this->servers->first();
if (! $foundServer) {
throw new \Exception('Server not found.');
}
$this->selectedServer = $foundServer;
- $this->serverId = $this->selectedServer->id;
+ $this->serverId = (string) $this->selectedServer->id;
}
$this->generateName();
}
- public function updatedServerId()
+ public function updatedServerId(): void
{
$this->selectedServer = $this->servers->find($this->serverId);
+ if (! $this->selectedServer) {
+ throw new \Exception('Server not found.');
+ }
$this->generateName();
}
- public function generateName()
+ public function generateName(): void
{
$name = data_get($this->selectedServer, 'name', new Cuid2);
$this->name = str("{$name}-{$this->network}")->kebab();
}
- public function submit()
+ public function submit(): mixed
{
try {
- $this->authorize('create', StandaloneDocker::class);
+ $this->authorize('create', $this->isSwarm ? SwarmDocker::class : StandaloneDocker::class);
$this->validate();
if ($this->isSwarm) {
$found = $this->selectedServer->swarmDockers()->where('network', $this->network)->first();
diff --git a/app/Livewire/Server/Destinations.php b/app/Livewire/Server/Destinations.php
index 117b43ad6..f3f142646 100644
--- a/app/Livewire/Server/Destinations.php
+++ b/app/Livewire/Server/Destinations.php
@@ -45,7 +45,7 @@ public function add($name)
} else {
SwarmDocker::create([
'name' => $this->server->name.'-'.$name,
- 'network' => $this->name,
+ 'network' => $name,
'server_id' => $this->server->id,
]);
}
diff --git a/resources/views/livewire/destination/index.blade.php b/resources/views/livewire/destination/index.blade.php
index aecd58d7a..d5026a651 100644
--- a/resources/views/livewire/destination/index.blade.php
+++ b/resources/views/livewire/destination/index.blade.php
@@ -14,34 +14,30 @@
Network endpoints to deploy your resources.
diff --git a/resources/views/livewire/server/destinations.blade.php b/resources/views/livewire/server/destinations.blade.php
index b5e8111e9..9d8a2b437 100644
--- a/resources/views/livewire/server/destinations.blade.php
+++ b/resources/views/livewire/server/destinations.blade.php
@@ -29,6 +29,9 @@
{{ data_get($docker, 'network') }}
@endforeach
+ @if ($server->standaloneDockers->isEmpty() && $server->swarmDockers->isEmpty())
+ No destinations configured for this server yet.
+ @endif
@if ($networks->count() > 0)
diff --git a/tests/Feature/ServerDestinationsPageTest.php b/tests/Feature/ServerDestinationsPageTest.php
new file mode 100644
index 000000000..3a1fb1790
--- /dev/null
+++ b/tests/Feature/ServerDestinationsPageTest.php
@@ -0,0 +1,107 @@
+ InstanceSettings::query()->create(['id' => 0]));
+
+ $this->user = User::factory()->create();
+ $this->team = Team::factory()->create();
+ $this->user->teams()->attach($this->team, ['role' => 'owner']);
+
+ $this->actingAs($this->user);
+ session(['currentTeam' => $this->team]);
+});
+
+test('destination creation modal can mount with selected team server even when global usable server list excludes it', function () {
+ $server = Server::factory()->create(['team_id' => $this->team->id]);
+ $server->settings()->update([
+ 'is_reachable' => true,
+ 'is_usable' => true,
+ 'is_build_server' => true,
+ ]);
+
+ StandaloneDocker::withoutEvents(fn () => $server->standaloneDockers()->delete());
+
+ Livewire::test(Docker::class, ['server_id' => (string) $server->id])
+ ->assertSet('selectedServer.id', $server->id)
+ ->assertSet('serverId', (string) $server->id);
+});
+
+test('server destinations page renders when selected server has no destinations', function () {
+ $server = Server::factory()->create(['team_id' => $this->team->id]);
+ $server->settings()->update([
+ 'is_reachable' => true,
+ 'is_usable' => true,
+ 'is_build_server' => true,
+ ]);
+
+ StandaloneDocker::withoutEvents(fn () => $server->standaloneDockers()->delete());
+
+ $this->get(route('server.destinations', ['server_uuid' => $server->uuid]))
+ ->assertSuccessful()
+ ->assertSee('Destinations')
+ ->assertSee('No destinations configured for this server yet.')
+ ->assertDontSee('Server not found.');
+});
+
+test('global destinations page does not render per-server empty states beside existing destinations', function () {
+ $serverWithDestination = Server::factory()->create(['team_id' => $this->team->id]);
+ $serverWithDestination->settings()->update([
+ 'is_reachable' => true,
+ 'is_usable' => true,
+ ]);
+
+ $serverWithoutDestination = Server::factory()->create(['team_id' => $this->team->id]);
+ $serverWithoutDestination->settings()->update([
+ 'is_reachable' => true,
+ 'is_usable' => true,
+ ]);
+ StandaloneDocker::withoutEvents(fn () => $serverWithoutDestination->standaloneDockers()->delete());
+
+ $this->get(route('destination.index'))
+ ->assertSuccessful()
+ ->assertSee($serverWithDestination->standaloneDockers()->first()->name)
+ ->assertDontSee('No destinations found.');
+});
+
+test('global destinations page renders a single empty state when no usable servers have destinations', function () {
+ $server = Server::factory()->create(['team_id' => $this->team->id]);
+ $server->settings()->update([
+ 'is_reachable' => true,
+ 'is_usable' => true,
+ ]);
+ StandaloneDocker::withoutEvents(fn () => $server->standaloneDockers()->delete());
+
+ $this->get(route('destination.index'))
+ ->assertSuccessful()
+ ->assertSee('No destinations found.');
+});
+
+test('adding a discovered swarm destination stores the selected network name', function () {
+ $server = Server::factory()->create(['team_id' => $this->team->id]);
+ $server->settings()->update([
+ 'is_reachable' => true,
+ 'is_usable' => true,
+ 'is_swarm_manager' => true,
+ ]);
+
+ Livewire::test(Destinations::class, ['server_uuid' => $server->uuid])
+ ->call('add', 'customer-network');
+
+ expect(SwarmDocker::where('server_id', $server->id)->where('network', 'customer-network')->exists())->toBeTrue();
+});