fix(hetzner): secure token-backed option fetches

Authorize stored Hetzner cloud provider tokens before fetching
firewalls or networks, hide provider error details, and keep server
creation intact if backup activation fails.

Collapse advanced Hetzner server options by default while preserving
visibility when advanced values are selected.
This commit is contained in:
Andras Bacsai committed 2026-07-07 14:24:15 +02:00
1 parent 2ad11fdd9a
commit d723a52d9c
6 files changed
+330 -85

No files matched your search

+87 -4
View File
@@ -74,6 +74,10 @@ class ByHetzner extends Component
public bool $enable_backups = false;
public bool $show_advanced_hetzner_options = false;
public bool $show_cloud_init_script = false;
public ?string $cloud_init_script = null;
public bool $save_cloud_init_script = false;
@@ -127,6 +131,8 @@ class ByHetzner extends Component
$this->save_cloud_init_script = false;
$this->cloud_init_script_name = null;
$this->selected_cloud_init_script_id = null;
$this->show_advanced_hetzner_options = false;
$this->show_cloud_init_script = false;
$this->selectedHetznerSshKeyIds = [];
$this->selectedHetznerFirewallIds = [];
$this->selectedHetznerNetworkIds = [];
@@ -185,6 +191,8 @@ class ByHetzner extends Component
'enable_ipv4' => 'required|boolean',
'enable_ipv6' => 'required|boolean',
'enable_backups' => 'required|boolean',
'show_advanced_hetzner_options' => 'boolean',
'show_cloud_init_script' => 'boolean',
'cloud_init_script' => ['nullable', 'string', new ValidCloudInitYaml],
'save_cloud_init_script' => 'boolean',
'cloud_init_script_name' => 'nullable|string|max:255',
@@ -444,6 +452,67 @@ class ByHetzner extends Component
return '€'.number_format($price * 0.2, 2);
}
public function getShouldShowAdvancedHetznerOptionsProperty(): bool
{
return $this->show_advanced_hetzner_options
|| $this->selectedHetznerSshKeyIds !== []
|| $this->selectedHetznerFirewallIds !== []
|| $this->selectedHetznerNetworkIds !== []
|| $this->enable_backups
|| ! $this->enable_ipv4
|| ! $this->enable_ipv6
|| $this->show_cloud_init_script
|| filled($this->cloud_init_script)
|| $this->save_cloud_init_script
|| filled($this->cloud_init_script_name)
|| filled($this->selected_cloud_init_script_id);
}
public function getAdvancedHetznerOptionsSummaryProperty(): array
{
$summary = [];
if (count($this->selectedHetznerSshKeyIds) > 0) {
$summary[] = count($this->selectedHetznerSshKeyIds).' extra SSH '.str('key')->plural(count($this->selectedHetznerSshKeyIds));
}
if (count($this->selectedHetznerFirewallIds) > 0) {
$summary[] = count($this->selectedHetznerFirewallIds).' '.str('firewall')->plural(count($this->selectedHetznerFirewallIds));
}
if (count($this->selectedHetznerNetworkIds) > 0) {
$summary[] = count($this->selectedHetznerNetworkIds).' private '.str('network')->plural(count($this->selectedHetznerNetworkIds));
}
if ($this->enable_backups) {
$summary[] = 'Backups on';
}
if (! $this->enable_ipv4 || ! $this->enable_ipv6) {
$summary[] = collect([
$this->enable_ipv4 ? 'IPv4' : null,
$this->enable_ipv6 ? 'IPv6' : null,
])->filter()->join(' + ') ?: 'No public IP';
}
if ($this->show_cloud_init_script || filled($this->cloud_init_script) || filled($this->selected_cloud_init_script_id)) {
$summary[] = 'Cloud-init';
}
return $summary;
}
public function toggleAdvancedHetznerOptions(): void
{
$this->show_advanced_hetzner_options = ! $this->show_advanced_hetzner_options;
}
public function showCloudInitScript(): void
{
$this->show_cloud_init_script = true;
$this->show_advanced_hetzner_options = true;
}
public function updatedSelectedLocation($value)
{
// Reset server type and image when location changes
@@ -475,6 +544,15 @@ class ByHetzner extends Component
$script = CloudInitScript::ownedByCurrentTeam()->findOrFail($value);
$this->cloud_init_script = $script->script;
$this->cloud_init_script_name = $script->name;
$this->show_cloud_init_script = true;
$this->show_advanced_hetzner_options = true;
}
}
public function updatedSaveCloudInitScript(bool $value): void
{
if (! $value) {
$this->cloud_init_script_name = null;
}
}
@@ -484,6 +562,7 @@ class ByHetzner extends Component
$this->cloud_init_script = '';
$this->cloud_init_script_name = '';
$this->save_cloud_init_script = false;
$this->show_cloud_init_script = false;
}
private function createHetznerServer(HetznerService $hetznerService): array
@@ -592,10 +671,6 @@ class ByHetzner extends Component
// Create server on Hetzner
$hetznerServer = $this->createHetznerServer($hetznerService);
if ($this->enable_backups) {
$hetznerService->enableServerBackup((int) $hetznerServer['id']);
}
// Determine IP address to use (prefer IPv4, fallback to IPv6)
$ipAddress = null;
if ($this->enable_ipv4 && isset($hetznerServer['public_net']['ipv4']['ip'])) {
@@ -624,6 +699,14 @@ class ByHetzner extends Component
$server->proxy->set('type', ProxyTypes::TRAEFIK->value);
$server->save();
if ($this->enable_backups) {
try {
$hetznerService->enableServerBackup((int) $hetznerServer['id']);
} catch (\Throwable $e) {
report($e);
}
}
if ($this->from_onboarding) {
// Complete the boarding when server is successfully created via Hetzner
currentTeam()->update([