Fixed multiple issues with GitHub App source creation and management:
1. **Fixed null property assignment error on component mount**
- Changed property types to nullable in Change component (appId, installationId, clientId, etc.)
- Updated validation rules to allow nullable values
- Allows mounting component with newly created GitHub Apps that don't have these fields set yet
2. **Fixed Livewire morphing error on manual creation**
- Modified createGithubAppManually() to redirect after saving
- Prevents "Cannot read properties of null" error when view structure changes
- Fields now properly populated after manual creation without requiring page refresh
3. **Fixed is_system_wide not being saved on creation**
- Removed backwards logic that only saved is_system_wide on cloud instances
- Added is_system_wide to GithubApp model casts for proper boolean handling
- System-wide checkbox now works correctly on self-hosted instances
4. **Fixed misleading preview deployment checkbox**
- Removed instantSave attribute from permission checkboxes in unconfigured state
- These are configuration options for GitHub App creation, not database fields
- Prevents "GitHub App updated" success message when nothing was actually saved
5. **Added validation for Refetch Permissions button**
- Validates App ID and Private Key are set before attempting to fetch
- Shows clear error messages: "Cannot fetch permissions. Please set the following required fields first: App ID, Private Key"
- Prevents crash when private key is null or invalid
6. **Better error handling for unsupported private key formats**
- Detects OpenSSH format keys vs RSA PEM format
- Shows helpful message: "Please use an RSA private key in PEM format (BEGIN RSA PRIVATE KEY). OpenSSH format keys are not supported."
- GitHub Apps require RSA PEM format, not OpenSSH format
7. **Made GitHub App view mobile responsive**
- Updated all flex layouts to stack vertically on mobile (flex-col sm:flex-row)
- Form fields, buttons, and sections now properly responsive
- No more cut-off fields on small screens
Added comprehensive test coverage:
- GithubSourceChangeTest.php with 7 tests
- GithubSourceCreateTest.php with 6 tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The loading icon was appearing during automatic background status checks
(every 10 seconds) even when users didn't click anything, which caused
confusion and made it seem like something was running unexpectedly.
Changes:
- Added manualCheckStatus() method to Application, Database, and Service
Heading components that wraps the checkStatus() call
- Updated status component buttons to call manualCheckStatus() instead
of checkStatus()
- Added wire:target="manualCheckStatus" to loading directives so the
loading icon only appears when users explicitly click the refresh button
- Added delay.shortest to prevent flickering on fast operations
The automatic wire:poll.10000ms="checkStatus" now runs silently in the
background without showing the loading icon, while manual refreshes
still provide visual feedback to the user.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit improves the visual presentation of service, application, and database logos on the new resource selection page:
- Remove grayscale filter: Logos now display in their original colors by default instead of being greyed out
- Dark mode support for SVGs: Updated SVG logos to use `fill="currentColor"` and added `text-black dark:text-white` wrapper for proper light/dark theme adaptation
- Consistent aspect ratios: Removed `aspect-square` and added `object-contain` to preserve original logo proportions
- Uniform sizing: Implemented fixed-size container (4.5rem × 4.5rem) with centered logo positioning to ensure all logos appear at consistent sizes regardless of intrinsic dimensions
- Improved mobile UX: Adjusted sticky search bar positioning from `top-10` to `top-20` to prevent navbar overlap
Files modified:
- resources/views/livewire/project/new/select.blade.php
- resources/views/components/resource-view.blade.php
- app/Livewire/Project/New/Select.php
- public/svgs/*.svg (12 SVG files updated with currentColor)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add a searchable category dropdown filter on the new resource page to help users filter services by category.
Features:
- Category dropdown positioned next to search input
- Auto-focus on search field when dropdown opens
- Case-insensitive category filtering
- Proper acronym formatting (AI, API, CI, etc. displayed in uppercase)
- Loading/disabled state while categories are being fetched
- Category search/filter within dropdown
- Alphabetical sorting (case-insensitive)
Backend changes:
- Extract unique categories from service templates
- Handle comma-separated categories
- Format common acronyms to uppercase
- Case-insensitive natural sorting
Frontend changes:
- Searchable dropdown component with Alpine.js
- Category filter integration with existing search
- Disabled state placeholder during loading
- Auto-focus behavior for better UX
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add ownedAndOnlySShKeys() method to filter out git-related keys
- Update Boarding component to use new filtering method
- Enhance datalist component with better multi-select and single-select handling
- Fix Alpine.js reactivity and improve UI interactions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When creating a Hetzner server from the onboarding view, the redirect
to the server details page was not working properly due to modal context.
The standard redirect() call doesn't handle navigation from within modals.
Changes:
- Add from_onboarding flag to ByHetzner component
- Use wire:navigate redirect when in onboarding mode
- Pass from_onboarding=true from boarding view
This ensures proper navigation to the newly created server page instead
of staying on the onboarding view.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When a docker compose service has multiple comma-separated domains, the
generate() method was only processing the first domain and truncating the rest.
The issue was that Url::fromString() can't parse comma-separated URLs - it only
parses the first one.
Fixed by:
1. Splitting comma-separated domains with explode(',', $domain_string)
2. Processing each domain individually in a foreach loop
3. Generating preview URLs for each domain using the same template/random/pr_id
4. Joining the results back with implode(',', $preview_fqdns)
This ensures all domains get properly transformed for preview deployments.
Example:
- Original: http://domain1.com,http://domain2.com
- Preview: http://57.domain1.com,http://57.domain2.com
- Before fix: http://57.domain1.com,http (truncated)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed three potential fatal errors where json_decode could return null:
1. save() method (lines 39-41): Added null coalescing to default to empty array,
and ensure service entry exists before writing domain
2. generate() method (line 56): Changed to use assoc flag consistently and
fallback to empty array
3. generate() method (lines 95-97): Same fix as save() - null coalescing and
service entry initialization
All json_decode calls now consistently:
- Use the assoc flag to return arrays (not objects)
- Fall back to empty array with ?: []
- Initialize service entry with ?? [] before writing
This prevents "Attempt to modify property of null" fatal errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Resolved merge conflicts between Livewire model binding refactoring and UI/CSS updates from next branch. Key integrations:
- Preserved unique HTML ID generation for form components
- Maintained wire:model bindings using $modelBinding
- Integrated new wire:dirty.class styles (border-l-warning pattern)
- Kept both syncData(true) and validateDockerComposeForInjection in StackForm
- Merged security tests and helper improvements from next
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit addresses a critical security issue where malicious Docker Compose
data was being saved to the database before validation occurred.
Problem:
- Service models were saved to database first
- Validation ran afterwards during parse()
- Malicious data persisted even when validation failed
- User saw error but damage was already done
Solution:
1. Created validateDockerComposeForInjection() to validate YAML before save
2. Added pre-save validation to all Service creation/update points:
- Livewire: DockerCompose.php, StackForm.php
- API: ServicesController.php (create, update, one-click)
3. Validates service names and volume paths (string + array formats)
4. Blocks shell metacharacters: backticks, $(), |, ;, &, >, <, newlines
Security fixes:
- Volume source paths (string format) - validated before save
- Volume source paths (array format) - validated before save
- Service names - validated before save
- Environment variable patterns - safe ${VAR} allowed, ${VAR:-$(cmd)} blocked
Testing:
- 60 security tests pass (176 assertions)
- PreSaveValidationTest.php: 15 tests for pre-save validation
- ValidateShellSafePathTest.php: 15 tests for core validation
- VolumeSecurityTest.php: 15 tests for volume parsing
- ServiceNameSecurityTest.php: 15 tests for service names
Related commits:
- Previous: Added validation during parse() phase
- This commit: Moves validation before database save
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit addresses a critical security vulnerability where low-privileged
users (members) could invite high-privileged users (admins/owners) to teams,
allowing them to escalate their own privileges through password reset.
Root Causes Fixed:
1. TeamPolicy authorization checks were commented out, allowing all team
members to manage invitations instead of just admins/owners
2. Missing role elevation checks in InviteLink component allowed members
to invite users with higher privileges
Security Fixes:
1. app/Policies/TeamPolicy.php
- Uncommented and enforced authorization checks for:
* update() - Only admins/owners can update team settings
* delete() - Only admins/owners can delete teams
* manageMembers() - Only admins/owners can manage team members
* viewAdmin() - Only admins/owners can view admin panel
* manageInvitations() - Only admins/owners can manage invitations
2. app/Livewire/Team/InviteLink.php
- Added explicit role elevation checks to prevent:
* Members from inviting admins or owners
* Admins from inviting owners (defense-in-depth)
- Validates that inviter has sufficient privileges for target role
Test Coverage:
1. tests/Feature/TeamPolicyTest.php
- 24 comprehensive tests covering all policy methods
- Tests for owner, admin, member, and non-member access
- Specific tests for the privilege escalation vulnerability
2. tests/Feature/TeamInvitationPrivilegeEscalationTest.php
- 11 tests covering all role elevation scenarios
- Tests member → admin/owner escalation (blocked)
- Tests admin → owner escalation (blocked)
- Tests valid invitation paths for each role
Impact:
- Prevents privilege escalation attacks
- Protects all Coolify instances from unauthorized access
- Enforces proper role hierarchy in team management
References:
- Identified by Aikido AI whitebox pentest service
- CVE: Pending assignment
- Severity: Critical
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed three issues preventing the "new image" quick action from working:
1. Frontend matching logic wasn't checking the quickcommand field
- Added check for item.quickcommand in the matching logic
- Now "new image" matches docker-image via its quickcommand "(type: new image)"
2. Search query remained populated after triggering selection flow
- Clear searchQuery in navigateToResourceCreation() to show selection UI
- This switches the UI from creatable items list to server selection
3. Redirect wasn't using Livewire's redirect method
- Changed from redirect()->route() to $this->redirect(route())
- Ensures proper Livewire component redirect behavior
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace manual regex parsing with DockerImageParser in ApplicationsController
- Fix double-decoration bug where image names like nginx@sha256:hash would
become nginx:hash@sha256 causing malformed references
- Add auto-parse feature in Livewire DockerImage component
- Users can now paste complete references like nginx:stable@sha256:abc123...
and fields auto-populate
- Update UI placeholder with examples: nginx, docker.io/nginx:latest,
ghcr.io/user/app:v1.2.3, nginx:stable@sha256:abc123...
- Add comprehensive unit tests for auto-parse functionality
- All tests passing (20 tests, 73 assertions)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The code was computing $imageTag with the 'sha256-' prefix for digest-based
images but then using $parser->getTag() directly when creating the Application,
which bypassed the prefix logic entirely.
This fix ensures that digest-based Docker images preserve their 'sha256-' prefix
by using the computed $imageTag variable instead of calling $parser->getTag()
directly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added authorization checks to 11 database-related Livewire components
that were loading sensitive database configuration without verifying
user permissions.
Changes:
- Added authorize('view', $database) to all 8 database type General.php mount() methods
- Added authorization to Configuration.php before loading database
- Added authorization to BackupEdit.php before loading backup config
- Added authorization to Import.php before loading database resource
This prevents unauthorized users from accessing database credentials,
connection strings, and configuration details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Nullable server + guard to avoid TypeError/NPE. Don't terminate the app, terminate the bug.
Changes:
- Made Server property nullable (?Server $server = null) in all 8 database General components
- Added guard clause in mount() to check for null server before accessing it
- Displays user-friendly error message when destination server is not configured
- Prevents crashes in methods like isLogDrainEnabled() and sslCertificates()
Fixed components:
- Mariadb, Dragonfly, Clickhouse, Keydb
- Mysql, Mongodb, Redis, Postgresql
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit addresses two critical issues with Docker Compose service management:
## Issue 1: Duplicate Services Created on Image Change
When changing the image in a docker-compose file, the parser was creating new
ServiceApplication/ServiceDatabase records instead of updating existing ones.
**Root Cause**: The parsers used `firstOrCreate()` with `['name', 'image', 'service_id']`,
meaning any image change would create a new record.
**Fix**: Remove `image` from `firstOrCreate()` queries and update it separately after
finding or creating the service record.
**Changes**:
- `bootstrap/helpers/parsers.php` (serviceParser v3): Fixed in presave loop (lines 1188-1203)
and main parsing loop (lines 1519-1539)
- `bootstrap/helpers/shared.php` (parseDockerComposeFile v2): Fixed null check logic
(lines 1308-1348)
## Issue 2: UI Not Refreshing After Changes
When compose file or domain was modified, the Configuration component wasn't receiving
events to refresh its data, requiring manual page refresh to see updates.
**Root Cause**: The Configuration component wasn't listening for refresh events dispatched
by child components (StackForm, EditDomain).
**Fix**: Add event listeners and dispatchers to enable real-time UI updates.
**Changes**:
- `app/Livewire/Project/Service/Configuration.php`: Added listeners for `refreshServices`
and `refresh` events (lines 36-37)
- `app/Livewire/Project/Service/EditDomain.php`: Added `refreshServices` dispatch (line 76)
- Note: `app/Livewire/Project/Service/StackForm.php` already had the dispatch
## Tests Added
- `tests/Unit/ServiceParserImageUpdateTest.php`: 4 tests verifying no duplicates created
- `tests/Unit/ServiceConfigurationRefreshTest.php`: 4 tests verifying event dispatching
All 8 new tests pass, and all existing unit tests continue to pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove global 'refresh' event listeners from all database General components
- Migrate Redis, MySQL, MariaDB, MongoDB, PostgreSQL, and KeyDB components to use explicit public properties instead of wire:model="database.field"
- Implement syncData() method in each component for manual data synchronization between properties and Eloquent models
- Update all validation rules, messages, and attributes to reference new property names
- Update Blade views to bind inputs to explicit properties (e.g., id="name" instead of id="database.name")
- Prepare codebase for disabling Livewire's legacy_model_binding configuration option
This refactoring resolves form field reset issues caused by global refresh events
and follows Livewire 3 best practices for component property management.
- Auto-select first SSH key when available instead of requiring explicit selection
- Remove disabled placeholder option from dropdown
- Prevents confusing error when user clicks "Use Selected Key" without changing dropdown
- Improves onboarding flow by having a sensible default selection
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add Hetzner Cloud server creation option to onboarding flow
- Change grid from 2 to 3 columns to accommodate all server options
- Mark both Hetzner and Remote Server as "Recommended"
- Fix Hetzner card height to match other cards
- Remove "select existing server" phase - onboarding always creates new servers
- Fix project loading on page refresh in Project Setup phase
- Fix browser back button navigation - remove aggressive restartBoarding() call
- Fix SSH key dropdown to not auto-select first key - require explicit selection
- Make checkpoint titles more prominent across all phases
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add centered, card-based layout with clean design
- Implement 3-step progress indicator component
- Add proper dark/light mode support following Coolify design system
- Implement Livewire URL state persistence for browser navigation
- Separate private key textareas for "Generate" vs "Add your own" modes
- Consistent checkpoint styling across all onboarding phases
- Enhanced typography with prominent titles (semibold, white in dark mode)
- Fixed state restoration on page refresh and browser back/forward navigation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enhanced the terminal server/container selection with a new searchable datalist component:
**Terminal View Changes:**
- Replaced `x-forms.select` with `x-forms.datalist` for server/container selection
- Added search functionality for filtering servers and containers
- Fixed form validation by adding hidden input for proper HTML5 validation
- Prevented error messages when clearing selection (sets to 'default')
**Datalist Component (Single Selection):**
- Implemented Alpine.js-powered dropdown with search functionality
- Added visual dropdown arrow that rotates when opened
- Proper entangle binding for wire:model support
- Keyboard support (Escape to close)
- Click outside to close behavior
- Disabled options filtering (skips disabled options)
- Consistent styling with input/textarea components
**Styling Improvements:**
- Explicit background colors: `bg-white` (light) and `dark:bg-coolgray-100` (dark)
- Proper ring border: `ring-1 ring-inset ring-neutral-200 dark:ring-coolgray-300`
- Focus states: `focus-within:ring-2 focus-within:ring-coollabs dark:focus-within:ring-warning`
- Text colors: `text-black dark:text-white`
- Added custom scrollbar styling for dropdown lists
- Wire:dirty state support for visual feedback
- Proper padding and spacing (`py-1.5`, `px-1`, `px-2`)
**Multiple Selection Mode:**
- Also updated for consistent styling and scrollbar support
- Added proper background colors and focus states
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add a 'Clear' button next to the cloud-init script dropdown that:
- Resets the dropdown to default (placeholder option)
- Clears the cloud-init script textarea
- Clears the script name input
- Unchecks the 'save script' checkbox
Improves UX by allowing users to quickly reset cloud-init fields
without manually clearing each field.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>