fix(terminal): allow container terminal for degraded services

Load the running containers of a service even when the service status is
degraded, instead of blocking the whole terminal. Also respect the server
terminal setting for service databases.

Closes #12174
This commit is contained in:
Andras Bacsai
2026-10-15 14:14:44 +02:00
parent 23a22e7970
commit a1d27ae6da
2 changed files with 53 additions and 4 deletions
@@ -76,9 +76,6 @@ class ExecuteContainerCommand extends Component
$this->type = 'service';
$this->resource = Service::ownedByCurrentTeam()->where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->authorize('view', $this->resource);
if (! $this->resource->isRunning()) {
$this->containersLoaded = true;
}
if ($this->resource->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->server);
}
@@ -140,7 +137,7 @@ class ExecuteContainerCommand extends Component
}
});
$this->resource->databases()->get()->each(function ($database) {
if ($database->isRunning()) {
if ($database->isRunning() && $this->resource->server->isTerminalEnabled()) {
$this->containers->push([
'server' => $this->resource->server,
'container' => [
@@ -8,6 +8,9 @@ use App\Models\InstanceSettings;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneDocker;
use App\Models\Team;
use App\Models\User;
@@ -106,6 +109,27 @@ function createTerminalApplication(Server $server, Team $team): Application
]);
}
/**
* A degraded service: app0 has exited, app1 and postgres are running.
*/
function createDegradedTerminalService(Server $server, Team $team): Service
{
$destination = StandaloneDocker::query()->where('server_id', $server->id)->firstOrFail();
$project = Project::factory()->create(['team_id' => $team->id]);
$environment = Environment::factory()->create(['project_id' => $project->id]);
$service = Service::factory()->create([
'environment_id' => $environment->id,
'server_id' => $server->id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
ServiceApplication::create(['service_id' => $service->id, 'name' => 'app0', 'status' => 'exited']);
ServiceApplication::create(['service_id' => $service->id, 'name' => 'app1', 'status' => 'running:unhealthy']);
ServiceDatabase::create(['service_id' => $service->id, 'name' => 'postgres', 'status' => 'running:healthy']);
return $service;
}
describe('Terminal::sendTerminalCommand', function () {
it('issues a token for a running container with a shell', function () {
fakeTerminalContainer('running');
@@ -269,4 +293,32 @@ describe('ExecuteContainerCommand', function () {
->assertDispatchedTo(Terminal::class, Terminal::AUTO_START_CANCELLED_EVENT)
->assertNotDispatched('send-terminal-command');
});
it('lists the running containers of a degraded service', function () {
Process::fake();
$service = createDegradedTerminalService($this->server, $this->team);
bindTerminalRouteParameters(['service_uuid' => $service->uuid]);
expect($service->fresh()->status)->toStartWith('degraded');
$component = Livewire::test(ExecuteContainerCommand::class)
->call('loadContainers')
->assertDispatchedTo(Terminal::class, Terminal::AUTO_START_CANCELLED_EVENT);
expect(collect($component->get('containerOptions'))->pluck('value')->all())->toBe([
"{$this->server->uuid}:app1-{$service->uuid}",
"{$this->server->uuid}:postgres-{$service->uuid}",
]);
});
it('lists no service containers when terminal access is disabled on the server', function () {
Process::fake();
$service = createDegradedTerminalService($this->server, $this->team);
$this->server->settings->update(['is_terminal_enabled' => false]);
bindTerminalRouteParameters(['service_uuid' => $service->uuid]);
Livewire::test(ExecuteContainerCommand::class)
->call('loadContainers')
->assertSet('containerOptions', []);
});
});