refactor: improve cloud-init script UX and remove description field

Changes:
1. Remove description field from cloud-init scripts
   - Updated migration to remove description column
   - Updated model to remove description from fillable array

2. Redesign script name input layout
   - Move script name input next to checkbox (always visible)
   - Remove conditional rendering - input always shown
   - Use placeholder instead of label for cleaner look

3. Fix dropdown type error
   - Replace wire:change event with wire:model.live
   - Use updatedSelectedCloudInitScriptId() lifecycle hook
   - Add "disabled" attribute to placeholder option
   - Properly handle empty string vs null in type casting

4. Improve validation
   - Require both script content AND name for saving
   - Remove description validation rule
   - Add selected_cloud_init_script_id validation

5. Auto-populate name when loading saved script
   - When user selects saved script, auto-fill the name field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2025-10-11 11:16:28 +02:00
parent e4bf8ab337
commit 6c0840d4e0
4 changed files with 15 additions and 23 deletions

View File

@@ -69,7 +69,7 @@ class ByHetzner extends Component
public ?string $cloud_init_script_name = null; public ?string $cloud_init_script_name = null;
public ?string $cloud_init_script_description = null; public ?int $selected_cloud_init_script_id = null;
#[Locked] #[Locked]
public Collection $saved_cloud_init_scripts; public Collection $saved_cloud_init_scripts;
@@ -155,8 +155,8 @@ class ByHetzner extends Component
'enable_ipv6' => 'required|boolean', 'enable_ipv6' => 'required|boolean',
'cloud_init_script' => 'nullable|string', 'cloud_init_script' => 'nullable|string',
'save_cloud_init_script' => 'boolean', 'save_cloud_init_script' => 'boolean',
'cloud_init_script_name' => 'required_if:save_cloud_init_script,true|nullable|string|max:255', 'cloud_init_script_name' => 'nullable|string|max:255',
'cloud_init_script_description' => 'nullable|string', 'selected_cloud_init_script_id' => 'nullable|integer|exists:cloud_init_scripts,id',
]); ]);
} }
@@ -394,11 +394,12 @@ class ByHetzner extends Component
ray('Image selected', $value); ray('Image selected', $value);
} }
public function loadCloudInitScript(?int $scriptId) public function updatedSelectedCloudInitScriptId($value)
{ {
if ($scriptId) { if ($value) {
$script = CloudInitScript::ownedByCurrentTeam()->findOrFail($scriptId); $script = CloudInitScript::ownedByCurrentTeam()->findOrFail($value);
$this->cloud_init_script = $script->script; $this->cloud_init_script = $script->script;
$this->cloud_init_script_name = $script->name;
} }
} }
@@ -496,14 +497,13 @@ class ByHetzner extends Component
} }
// Save cloud-init script if requested // Save cloud-init script if requested
if ($this->save_cloud_init_script && ! empty($this->cloud_init_script)) { if ($this->save_cloud_init_script && ! empty($this->cloud_init_script) && ! empty($this->cloud_init_script_name)) {
$this->authorize('create', CloudInitScript::class); $this->authorize('create', CloudInitScript::class);
CloudInitScript::create([ CloudInitScript::create([
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
'name' => $this->cloud_init_script_name, 'name' => $this->cloud_init_script_name,
'script' => $this->cloud_init_script, 'script' => $this->cloud_init_script,
'description' => $this->cloud_init_script_description,
]); ]);
} }

View File

@@ -10,7 +10,6 @@ class CloudInitScript extends Model
'team_id', 'team_id',
'name', 'name',
'script', 'script',
'description',
]; ];
protected function casts(): array protected function casts(): array

View File

@@ -16,7 +16,6 @@ return new class extends Migration
$table->foreignId('team_id')->constrained()->onDelete('cascade'); $table->foreignId('team_id')->constrained()->onDelete('cascade');
$table->string('name'); $table->string('name');
$table->text('script'); // Encrypted in the model $table->text('script'); // Encrypted in the model
$table->text('description')->nullable();
$table->timestamps(); $table->timestamps();
$table->index('team_id'); $table->index('team_id');

View File

@@ -160,9 +160,9 @@
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<label class="text-sm font-medium">Cloud-Init Script (Optional)</label> <label class="text-sm font-medium">Cloud-Init Script (Optional)</label>
@if ($saved_cloud_init_scripts->count() > 0) @if ($saved_cloud_init_scripts->count() > 0)
<x-forms.select id="load_script" wire:change="loadCloudInitScript($event.target.value)" <x-forms.select wire:model.live="selected_cloud_init_script_id"
label="" helper=""> label="" helper="">
<option value="">Load saved script...</option> <option value="" disabled>Load saved script...</option>
@foreach ($saved_cloud_init_scripts as $script) @foreach ($saved_cloud_init_scripts as $script)
<option value="{{ $script->id }}">{{ $script->name }}</option> <option value="{{ $script->id }}">{{ $script->name }}</option>
@endforeach @endforeach
@@ -173,17 +173,11 @@
helper="Add a cloud-init script to run when the server is created. See Hetzner's documentation for details." helper="Add a cloud-init script to run when the server is created. See Hetzner's documentation for details."
rows="8" /> rows="8" />
<div class="flex flex-col gap-2"> <div class="flex items-center gap-2">
<x-forms.checkbox id="save_cloud_init_script" label="Save this script for later use" <x-forms.checkbox id="save_cloud_init_script" label="Save this script for later use" />
helper="Save this cloud-init script to your team's library for reuse" /> <div class="flex-1">
<x-forms.input id="cloud_init_script_name" label="" placeholder="Script name..." />
@if ($save_cloud_init_script) </div>
<div class="flex flex-col gap-2 ml-6">
<x-forms.input id="cloud_init_script_name" label="Script Name" required />
<x-forms.textarea id="cloud_init_script_description" label="Description (Optional)"
rows="2" />
</div>
@endif
</div> </div>
</div> </div>