mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(hetzner): show out-of-stock server types as disabled in the server form
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
101444b8ca
commit
28f44d3aa0
@@ -323,7 +323,7 @@ class ByHetzner extends Component
|
||||
$hetznerService = new HetznerService($token);
|
||||
|
||||
$this->locations = $hetznerService->getLocations();
|
||||
$this->serverTypes = $hetznerService->getServerTypes();
|
||||
$this->serverTypes = $hetznerService->getServerTypes(includeOutOfStock: true);
|
||||
|
||||
// Get images and sort by name
|
||||
$images = $hetznerService->getImages();
|
||||
@@ -385,7 +385,7 @@ class ByHetzner extends Component
|
||||
public function getAvailableServerTypesProperty()
|
||||
{
|
||||
if (! $this->selected_location) {
|
||||
return $this->serverTypes;
|
||||
return [];
|
||||
}
|
||||
|
||||
$filtered = collect($this->serverTypes)
|
||||
@@ -400,15 +400,24 @@ class ByHetzner extends Component
|
||||
})
|
||||
->map(function ($serverType) {
|
||||
$serverType['cpu_vendor_info'] = $this->getCpuVendorInfo($serverType);
|
||||
$serverType['out_of_stock'] = $this->isServerTypeOutOfStock($serverType);
|
||||
|
||||
return $serverType;
|
||||
})
|
||||
->sortBy('out_of_stock')
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
private function isServerTypeOutOfStock(array $serverType): bool
|
||||
{
|
||||
$typeLocation = collect($serverType['locations'] ?? [])->firstWhere('name', $this->selected_location);
|
||||
|
||||
return ($typeLocation['available'] ?? true) === false;
|
||||
}
|
||||
|
||||
public function getAvailableImagesProperty()
|
||||
{
|
||||
|
||||
@@ -670,6 +679,13 @@ class ByHetzner extends Component
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$selectedServerType = collect($this->serverTypes)->firstWhere('name', $this->selected_server_type);
|
||||
if ($selectedServerType && $this->isServerTypeOutOfStock($selectedServerType)) {
|
||||
$this->addError('selected_server_type', 'This server type is out of stock in the selected location. Select a different server type or location.');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->enable_ipv4 && ! $this->enable_ipv6) {
|
||||
$this->addError('enable_ipv4', 'Enable at least one public IP protocol.');
|
||||
$this->addError('enable_ipv6', 'Enable at least one public IP protocol.');
|
||||
|
||||
@@ -121,16 +121,18 @@ class HetznerService
|
||||
/**
|
||||
* Server types that can be ordered now. Each type keeps only the locations
|
||||
* where it is in stock and not deprecated, and types with no such location are removed.
|
||||
* With $includeOutOfStock, locations where the type is out of stock are kept.
|
||||
*/
|
||||
public function getServerTypes(?string $location = null): array
|
||||
public function getServerTypes(?string $location = null, bool $includeOutOfStock = false): array
|
||||
{
|
||||
$types = $this->requestPaginated('get', '/server_types', 'server_types');
|
||||
|
||||
return collect($types)
|
||||
->reject(fn (array $type) => ($type['deprecated'] ?? false) === true)
|
||||
->map(function (array $type) {
|
||||
->map(function (array $type) use ($includeOutOfStock) {
|
||||
$type['locations'] = collect($type['locations'] ?? [])
|
||||
->filter(fn (array $typeLocation) => ($typeLocation['available'] ?? true) === true && empty($typeLocation['deprecation']))
|
||||
->filter(fn (array $typeLocation) => empty($typeLocation['deprecation'])
|
||||
&& ($includeOutOfStock || ($typeLocation['available'] ?? true) === true))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
|
||||
@@ -42,9 +42,11 @@
|
||||
. ' · ' . ($type['cores'] ?? '?') . ' vCPU'
|
||||
. ' · ' . ($type['memory'] ?? '?') . ' GB RAM'
|
||||
. ' · ' . ($type['disk'] ?? '?') . ' GB';
|
||||
$label = $price ? $label . ' · €' . number_format($price, 2) . '/mo' : $label;
|
||||
return [
|
||||
'value' => $type['name'],
|
||||
'label' => $price ? $label . ' · €' . number_format($price, 2) . '/mo' : $label,
|
||||
'label' => $type['out_of_stock'] ? $label . ' · Out of stock' : $label,
|
||||
'disabled' => $type['out_of_stock'],
|
||||
];
|
||||
})->values()->all();
|
||||
$imageOptions = collect($this->availableImages)->map(fn ($image) => [
|
||||
|
||||
@@ -363,4 +363,57 @@ describe('Hetzner data loading', function () {
|
||||
|
||||
Http::assertNothingSent();
|
||||
});
|
||||
test('lists server types that are out of stock in the selected location as out of stock', function () {
|
||||
Http::fake([
|
||||
'https://api.hetzner.cloud/v1/server_types*' => Http::response([
|
||||
'server_types' => [
|
||||
['id' => 1, 'name' => 'cx22', 'locations' => [
|
||||
['id' => 1, 'name' => 'nbg1', 'available' => false, 'deprecation' => null],
|
||||
['id' => 2, 'name' => 'fsn1', 'available' => true, 'deprecation' => null],
|
||||
]],
|
||||
['id' => 2, 'name' => 'cx32', 'locations' => [
|
||||
['id' => 1, 'name' => 'nbg1', 'available' => true, 'deprecation' => null],
|
||||
]],
|
||||
['id' => 3, 'name' => 'cx11', 'locations' => [
|
||||
['id' => 1, 'name' => 'nbg1', 'available' => true, 'deprecation' => ['announced' => '2025-09-24T00:00:00+00:00', 'unavailable_after' => '2025-12-31T00:00:00+00:00']],
|
||||
]],
|
||||
],
|
||||
'meta' => ['pagination' => ['next_page' => null]],
|
||||
], 200),
|
||||
'https://api.hetzner.cloud/v1/*' => Http::response(['locations' => [], 'images' => [], 'ssh_keys' => [], 'firewalls' => [], 'networks' => [], 'meta' => ['pagination' => ['next_page' => null]]], 200),
|
||||
]);
|
||||
|
||||
$component = Livewire::test(ByHetzner::class, ['selectedTokenUuid' => $this->hetznerToken->uuid])
|
||||
->call('loadHetznerData')
|
||||
->set('selected_location', 'nbg1');
|
||||
|
||||
expect(collect($component->instance()->availableServerTypes)->mapWithKeys(fn ($type) => [$type['name'] => $type['out_of_stock']])->all())
|
||||
->toBe(['cx32' => false, 'cx22' => true]);
|
||||
});
|
||||
|
||||
test('does not create a server with a server type that is out of stock in the selected location', function () {
|
||||
Http::fake([
|
||||
'https://api.hetzner.cloud/v1/server_types*' => Http::response([
|
||||
'server_types' => [
|
||||
['id' => 1, 'name' => 'cx22', 'locations' => [
|
||||
['id' => 1, 'name' => 'nbg1', 'available' => false, 'deprecation' => null],
|
||||
]],
|
||||
],
|
||||
'meta' => ['pagination' => ['next_page' => null]],
|
||||
], 200),
|
||||
'https://api.hetzner.cloud/v1/*' => Http::response(['locations' => [], 'images' => [], 'ssh_keys' => [], 'firewalls' => [], 'networks' => [], 'meta' => ['pagination' => ['next_page' => null]]], 200),
|
||||
]);
|
||||
|
||||
Livewire::test(ByHetzner::class, ['selectedTokenUuid' => $this->hetznerToken->uuid])
|
||||
->call('loadHetznerData')
|
||||
->set('server_name', 'test-server')
|
||||
->set('selected_location', 'nbg1')
|
||||
->set('selected_server_type', 'cx22')
|
||||
->set('selected_image', 15512617)
|
||||
->set('private_key_id', $this->privateKey->id)
|
||||
->call('submit')
|
||||
->assertHasErrors(['selected_server_type']);
|
||||
|
||||
Http::assertNotSent(fn ($request) => $request->method() === 'POST');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user