feat(ui): add container name prefix to application container settings

This commit is contained in:
peaklabs-dev
2026-09-09 15:01:57 +02:00
committed by 🏔️ Peak
parent 266d414a04
commit 7efece06c4
4 changed files with 99 additions and 4 deletions
@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Application;
use App\Models\Application;
use App\Models\ApplicationSetting;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
@@ -69,6 +70,9 @@ class Advanced extends Component
#[Validate(['string', 'nullable'])]
public ?string $customInternalName = null;
#[Validate(['string', 'nullable', 'max:47'])]
public ?string $customContainerNamePrefix = null;
#[Validate(['boolean'])]
public bool $isGzipEnabled = true;
@@ -111,6 +115,7 @@ class Advanced extends Component
$this->application->settings->is_build_server_enabled = $this->isBuildServerEnabled;
$this->application->settings->is_consistent_container_name_enabled = $this->isConsistentContainerNameEnabled;
$this->application->settings->custom_internal_name = $this->customInternalName;
$this->application->settings->custom_container_name_prefix = $this->customContainerNamePrefix;
$this->application->settings->is_gzip_enabled = $this->isGzipEnabled;
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
@@ -137,6 +142,7 @@ class Advanced extends Component
$this->isBuildServerEnabled = $this->application->settings->is_build_server_enabled;
$this->isConsistentContainerNameEnabled = $this->application->settings->is_consistent_container_name_enabled;
$this->customInternalName = $this->application->settings->custom_internal_name;
$this->customContainerNamePrefix = $this->application->settings->custom_container_name_prefix;
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
$this->disableBuildCache = $this->application->settings->disable_build_cache;
@@ -258,6 +264,28 @@ class Advanced extends Component
}
}
public function saveCustomNamePrefix()
{
try {
$this->authorize('update', $this->application);
$this->customContainerNamePrefix = str($this->customContainerNamePrefix)->slug()->value() ?: null;
if ($this->customContainerNamePrefix && ApplicationSetting::isContainerNamePrefixInUse($this->customContainerNamePrefix, $this->application->destination->server, $this->application->id)) {
$this->customContainerNamePrefix = $this->application->settings->custom_container_name_prefix;
$this->dispatch('error', 'This container name prefix is already in use by another application on this Coolify instance.');
return;
}
$this->syncData(true);
$this->dispatch('success', 'Container name prefix saved.');
$this->dispatch('configurationChanged');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function saveStopGracePeriod()
{
try {
@@ -45,10 +45,21 @@
['value' => true, 'label' => 'Consistent name (no rolling updates)'],
]" :disabled="! $canUpdate" />
@if ($isConsistentContainerNameEnabled === true)
<x-forms.input
helper="You can add a custom name for your container.<br><br>The name is saved automatically and converted to slug format. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
id="customInternalName" label="Custom container name" canGate="update"
wire:change="saveCustomName" :canResource="$application" />
<form wire:submit="saveCustomName" class="w-full">
<x-unsaved-bar action="saveCustomName" targets="customInternalName" />
<x-forms.input
helper="You can add a custom name for your container.<br><br>The name is converted to slug format when saved. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
id="customInternalName" label="Custom container name" canGate="update"
:canResource="$application" />
</form>
@else
<form wire:submit="saveCustomNamePrefix" class="w-full">
<x-unsaved-bar action="saveCustomNamePrefix" targets="customContainerNamePrefix" />
<x-forms.input
helper="Optional prefix for generated container names. Containers are named <span class='font-bold'>prefix-timestamp</span>, for example <span class='font-bold'>shop-api-20260908T141530</span>, instead of starting with <span class='font-bold'>{{ $application->uuid }}</span>.<br><br>The prefix is converted to slug format when saved, can be up to 47 characters and must be unique on this server. Rolling updates keep working."
id="customContainerNamePrefix" label="Container name prefix" placeholder="e.g. my-api"
canGate="update" :canResource="$application" />
</form>
@endif
</div>
</x-application.settings-section>
@@ -247,6 +247,18 @@ test('member cannot submit application advanced settings', function () {
->assertDispatched('error');
});
test('member cannot save the application container name prefix', function () {
$this->actingAs($this->member);
session(['currentTeam' => $this->team]);
Livewire::test(ApplicationAdvanced::class, ['application' => $this->application])
->set('customContainerNamePrefix', 'member-prefix')
->call('saveCustomNamePrefix')
->assertDispatched('error');
expect($this->application->settings->fresh()->custom_container_name_prefix)->toBeNull();
});
test('the private application advanced syncData helper is not remotely callable', function () {
$this->actingAs($this->member);
session(['currentTeam' => $this->team]);
@@ -104,3 +104,47 @@ it('only shows the custom container name for consistent naming', function () {
->set('isConsistentContainerNameEnabled', true)
->assertSee('Custom container name');
});
it('saves a slugged container name prefix in generated naming mode', function () {
$otherTeamApplication = createApplicationForContainerNamingTest();
$otherTeamApplication->settings->update(['custom_container_name_prefix' => 'my-api']);
$application = createApplicationForContainerNamingTest();
$application->settings->update(['custom_internal_name' => 'legacy-name']);
$application = $application->fresh(['environment.project', 'settings', 'destination']);
Livewire::test(Advanced::class, ['application' => $application])
->assertSee('Container name prefix')
->set('customContainerNamePrefix', 'My API')
->call('saveCustomNamePrefix')
->assertDispatched('success')
->assertSet('customContainerNamePrefix', 'my-api');
$settings = $application->settings()->first();
expect($settings->custom_container_name_prefix)->toBe('my-api')
->and($settings->custom_internal_name)->toBe('legacy-name');
});
it('rejects a container name prefix already used on the server', function () {
$application = createApplicationForContainerNamingTest();
$sibling = fn () => Application::factory()->create([
'environment_id' => $application->environment_id,
'destination_id' => $application->destination_id,
'destination_type' => $application->destination_type,
]);
$prefixedApplication = $sibling();
$prefixedApplication->settings->update(['custom_container_name_prefix' => 'shared-prefix']);
$sibling()->settings->update(['custom_internal_name' => 'api']);
$application = $application->fresh(['environment.project', 'settings', 'destination']);
$component = Livewire::test(Advanced::class, ['application' => $application]);
foreach (['shared-prefix', 'api', $prefixedApplication->uuid] as $takenPrefix) {
$component->set('customContainerNamePrefix', $takenPrefix)
->call('saveCustomNamePrefix')
->assertDispatched('error')
->assertSet('customContainerNamePrefix', null);
}
expect($application->settings()->first()->custom_container_name_prefix)->toBeNull();
});