From 5463f4d4961cfccab6ddbc9fa341c74d151261a0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 11 Oct 2025 12:42:09 +0200 Subject: [PATCH] feat: add cloud-init scripts management UI in Security section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive cloud-init script management interface in the Security section, allowing users to create, edit, delete, and reuse cloud-init scripts across their team. New Components: - CloudInitScripts: Main listing page with grid view of scripts - CloudInitScriptForm: Modal form for create/edit operations Features: - Create new cloud-init scripts with name and content - Edit existing scripts - Delete scripts with confirmation (requires typing script name) - View script preview (first 200 characters) - Scripts are encrypted in database - Full authorization using CloudInitScriptPolicy - Real-time updates via Livewire events UI Location: - Added to Security section nav: /security/cloud-init-scripts - Positioned between Cloud Tokens and API Tokens - Follows existing security UI patterns Files Created: - app/Livewire/Security/CloudInitScripts.php - app/Livewire/Security/CloudInitScriptForm.php - resources/views/livewire/security/cloud-init-scripts.blade.php - resources/views/livewire/security/cloud-init-script-form.blade.php Files Modified: - routes/web.php - Added route - resources/views/components/security/navbar.blade.php - Added nav link 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Livewire/Security/CloudInitScriptForm.php | 97 +++++++++++++++++++ app/Livewire/Security/CloudInitScripts.php | 52 ++++++++++ .../components/security/navbar.blade.php | 5 + .../security/cloud-init-script-form.blade.php | 17 ++++ .../security/cloud-init-scripts.blade.php | 57 +++++++++++ routes/web.php | 2 + 6 files changed, 230 insertions(+) create mode 100644 app/Livewire/Security/CloudInitScriptForm.php create mode 100644 app/Livewire/Security/CloudInitScripts.php create mode 100644 resources/views/livewire/security/cloud-init-script-form.blade.php create mode 100644 resources/views/livewire/security/cloud-init-scripts.blade.php diff --git a/app/Livewire/Security/CloudInitScriptForm.php b/app/Livewire/Security/CloudInitScriptForm.php new file mode 100644 index 000000000..5307e28b3 --- /dev/null +++ b/app/Livewire/Security/CloudInitScriptForm.php @@ -0,0 +1,97 @@ +scriptId = $scriptId; + $cloudInitScript = CloudInitScript::ownedByCurrentTeam()->findOrFail($scriptId); + $this->authorize('update', $cloudInitScript); + + $this->name = $cloudInitScript->name; + $this->script = $cloudInitScript->script; + } else { + $this->authorize('create', CloudInitScript::class); + } + } + + protected function rules(): array + { + return [ + 'name' => 'required|string|max:255', + 'script' => 'required|string', + ]; + } + + protected function messages(): array + { + return [ + 'name.required' => 'Script name is required.', + 'name.max' => 'Script name cannot exceed 255 characters.', + 'script.required' => 'Cloud-init script content is required.', + ]; + } + + public function save() + { + $this->validate(); + + try { + if ($this->scriptId) { + // Update existing script + $cloudInitScript = CloudInitScript::ownedByCurrentTeam()->findOrFail($this->scriptId); + $this->authorize('update', $cloudInitScript); + + $cloudInitScript->update([ + 'name' => $this->name, + 'script' => $this->script, + ]); + + $message = 'Cloud-init script updated successfully.'; + } else { + // Create new script + $this->authorize('create', CloudInitScript::class); + + CloudInitScript::create([ + 'team_id' => currentTeam()->id, + 'name' => $this->name, + 'script' => $this->script, + ]); + + $message = 'Cloud-init script created successfully.'; + } + + $this->reset(['name', 'script', 'scriptId']); + $this->dispatch('scriptSaved'); + $this->dispatch('success', $message); + + if ($this->modal_mode) { + $this->dispatch('closeModal'); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.security.cloud-init-script-form'); + } +} diff --git a/app/Livewire/Security/CloudInitScripts.php b/app/Livewire/Security/CloudInitScripts.php new file mode 100644 index 000000000..13bcf2caa --- /dev/null +++ b/app/Livewire/Security/CloudInitScripts.php @@ -0,0 +1,52 @@ +authorize('viewAny', CloudInitScript::class); + $this->loadScripts(); + } + + public function getListeners() + { + return [ + 'scriptSaved' => 'loadScripts', + ]; + } + + public function loadScripts() + { + $this->scripts = CloudInitScript::ownedByCurrentTeam()->orderBy('created_at', 'desc')->get(); + } + + public function deleteScript(int $scriptId) + { + try { + $script = CloudInitScript::ownedByCurrentTeam()->findOrFail($scriptId); + $this->authorize('delete', $script); + + $script->delete(); + $this->loadScripts(); + + $this->dispatch('success', 'Cloud-init script deleted successfully.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.security.cloud-init-scripts'); + } +} diff --git a/resources/views/components/security/navbar.blade.php b/resources/views/components/security/navbar.blade.php index b0dfdd242..425c96d74 100644 --- a/resources/views/components/security/navbar.blade.php +++ b/resources/views/components/security/navbar.blade.php @@ -11,6 +11,11 @@ @endcan + @can('viewAny', App\Models\CloudInitScript::class) + + + + @endcan diff --git a/resources/views/livewire/security/cloud-init-script-form.blade.php b/resources/views/livewire/security/cloud-init-script-form.blade.php new file mode 100644 index 000000000..545c49a7f --- /dev/null +++ b/resources/views/livewire/security/cloud-init-script-form.blade.php @@ -0,0 +1,17 @@ +
+ + + + +
+ @if ($modal_mode) + + Cancel + + @endif + + {{ $scriptId ? 'Update Script' : 'Create Script' }} + +
+ diff --git a/resources/views/livewire/security/cloud-init-scripts.blade.php b/resources/views/livewire/security/cloud-init-scripts.blade.php new file mode 100644 index 000000000..910b87b32 --- /dev/null +++ b/resources/views/livewire/security/cloud-init-scripts.blade.php @@ -0,0 +1,57 @@ +
+ +
+

Cloud-Init Scripts

+ @can('create', App\Models\CloudInitScript::class) + + + + @endcan +
+
Manage reusable cloud-init scripts for server initialization.
+ +
+ @forelse ($scripts as $script) +
+
+
+
+
{{ $script->name }}
+
+ Created {{ $script->created_at->diffForHumans() }} +
+
+
+ +
+
Script Preview:
+
{{ Str::limit($script->script, 200) }}
+
+ +
+ @can('update', $script) + + + + @endcan + + @can('delete', $script) + + @endcan +
+
+
+ @empty +
No cloud-init scripts found. Create one to get started.
+ @endforelse +
+
diff --git a/routes/web.php b/routes/web.php index 312bfb193..fd185496d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -34,6 +34,7 @@ use App\Livewire\Project\Shared\ScheduledTask\Show as ScheduledTaskShow; use App\Livewire\Project\Show as ProjectShow; use App\Livewire\Security\ApiTokens; +use App\Livewire\Security\CloudInitScripts; use App\Livewire\Security\CloudTokens; use App\Livewire\Security\PrivateKey\Index as SecurityPrivateKeyIndex; use App\Livewire\Security\PrivateKey\Show as SecurityPrivateKeyShow; @@ -275,6 +276,7 @@ Route::get('/security/private-key/{private_key_uuid}', SecurityPrivateKeyShow::class)->name('security.private-key.show'); Route::get('/security/cloud-tokens', CloudTokens::class)->name('security.cloud-tokens'); + Route::get('/security/cloud-init-scripts', CloudInitScripts::class)->name('security.cloud-init-scripts'); Route::get('/security/api-tokens', ApiTokens::class)->name('security.api-tokens'); });