'submit', 'refreshEnvs', 'environmentVariableDeleted' => 'refreshEnvs', ]; public function mount() { $this->is_env_sorting_enabled = data_get($this->resource, 'settings.is_env_sorting_enabled', false); $this->use_build_secrets = data_get($this->resource, 'settings.use_build_secrets', false); $this->resourceClass = get_class($this->resource); $resourceWithPreviews = [\App\Models\Application::class]; $simpleDockerfile = filled(data_get($this->resource, 'dockerfile')); if (str($this->resourceClass)->contains($resourceWithPreviews) && ! $simpleDockerfile) { $this->showPreview = true; } $this->getDevView(); } public function instantSave() { try { $this->authorize('manageEnvironment', $this->resource); $this->resource->settings->is_env_sorting_enabled = $this->is_env_sorting_enabled; $this->resource->settings->use_build_secrets = $this->use_build_secrets; $this->resource->settings->save(); $this->getDevView(); $this->dispatch('success', 'Environment variable settings updated.'); } catch (\Throwable $e) { return handleError($e, $this); } } public function getEnvironmentVariablesProperty() { $query = $this->resource->environment_variables() ->orderByRaw("CASE WHEN is_required = true AND (value IS NULL OR value = '') THEN 0 ELSE 1 END"); if ($this->is_env_sorting_enabled) { $query->orderBy('key'); } else { $query->orderBy('order'); } return $query->get(); } public function getEnvironmentVariablesPreviewProperty() { $query = $this->resource->environment_variables_preview() ->orderByRaw("CASE WHEN is_required = true AND (value IS NULL OR value = '') THEN 0 ELSE 1 END"); if ($this->is_env_sorting_enabled) { $query->orderBy('key'); } else { $query->orderBy('order'); } return $query->get(); } public function getHardcodedEnvironmentVariablesProperty() { return $this->getHardcodedVariables(false); } public function getHardcodedEnvironmentVariablesPreviewProperty() { return $this->getHardcodedVariables(true); } protected function getHardcodedVariables(bool $isPreview) { // Only for services and docker-compose applications if ($this->resource->type() !== 'service' && ($this->resourceClass !== 'App\Models\Application' || ($this->resourceClass === 'App\Models\Application' && $this->resource->build_pack !== 'dockercompose'))) { return collect([]); } $dockerComposeRaw = $this->resource->docker_compose_raw ?? $this->resource->docker_compose; if (blank($dockerComposeRaw)) { return collect([]); } // Extract all hard-coded variables $hardcodedVars = extractHardcodedEnvironmentVariables($dockerComposeRaw); // Filter out magic variables (SERVICE_FQDN_*, SERVICE_URL_*, SERVICE_NAME_*) $hardcodedVars = $hardcodedVars->filter(function ($var) { $key = $var['key']; return ! str($key)->startsWith(['SERVICE_FQDN_', 'SERVICE_URL_', 'SERVICE_NAME_']); }); // Filter out variables that exist in database (user has overridden/managed them) // For preview, check against preview variables; for production, check against production variables if ($isPreview) { $managedKeys = $this->resource->environment_variables_preview()->pluck('key')->toArray(); } else { $managedKeys = $this->resource->environment_variables()->where('is_preview', false)->pluck('key')->toArray(); } $hardcodedVars = $hardcodedVars->filter(function ($var) use ($managedKeys) { return ! in_array($var['key'], $managedKeys); }); // Apply sorting based on is_env_sorting_enabled if ($this->is_env_sorting_enabled) { $hardcodedVars = $hardcodedVars->sortBy('key')->values(); } // Otherwise keep order from docker-compose file return $hardcodedVars; } public function getDevView() { $this->variables = $this->formatEnvironmentVariables($this->environmentVariables); if ($this->showPreview) { $this->variablesPreview = $this->formatEnvironmentVariables($this->environmentVariablesPreview); } } private function formatEnvironmentVariables($variables) { return $variables->map(function ($item) { if ($item->is_shown_once) { return "$item->key=(Locked Secret, delete and add again to change)"; } if ($item->is_multiline) { return "$item->key=(Multiline environment variable, edit in normal view)"; } return "$item->key=$item->value"; })->join("\n"); } public function switch() { $this->view = $this->view === 'normal' ? 'dev' : 'normal'; $this->getDevView(); } public function submit($data = null) { try { $this->authorize('manageEnvironment', $this->resource); if ($data === null) { $this->handleBulkSubmit(); } else { $this->handleSingleSubmit($data); } $this->updateOrder(); $this->getDevView(); } catch (\Throwable $e) { return handleError($e, $this); } finally { $this->refreshEnvs(); } } private function updateOrder() { $variables = parseEnvFormatToArray($this->variables); $order = 1; foreach ($variables as $key => $value) { $env = $this->resource->environment_variables()->where('key', $key)->first(); if ($env) { $env->order = $order; $env->save(); } $order++; } if ($this->showPreview) { $previewVariables = parseEnvFormatToArray($this->variablesPreview); $order = 1; foreach ($previewVariables as $key => $value) { $env = $this->resource->environment_variables_preview()->where('key', $key)->first(); if ($env) { $env->order = $order; $env->save(); } $order++; } } } private function handleBulkSubmit() { $variables = parseEnvFormatToArray($this->variables); $changesMade = false; $errorOccurred = false; // Try to delete removed variables $deletedCount = $this->deleteRemovedVariables(false, $variables); if ($deletedCount > 0) { $changesMade = true; } elseif ($deletedCount === 0 && $this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->exists()) { // If we tried to delete but couldn't (due to Docker Compose), mark as error $errorOccurred = true; } // Update or create variables $updatedCount = $this->updateOrCreateVariables(false, $variables); if ($updatedCount > 0) { $changesMade = true; } if ($this->showPreview) { $previewVariables = parseEnvFormatToArray($this->variablesPreview); // Try to delete removed preview variables $deletedPreviewCount = $this->deleteRemovedVariables(true, $previewVariables); if ($deletedPreviewCount > 0) { $changesMade = true; } elseif ($deletedPreviewCount === 0 && $this->resource->environment_variables_preview()->whereNotIn('key', array_keys($previewVariables))->exists()) { // If we tried to delete but couldn't (due to Docker Compose), mark as error $errorOccurred = true; } // Update or create preview variables $updatedPreviewCount = $this->updateOrCreateVariables(true, $previewVariables); if ($updatedPreviewCount > 0) { $changesMade = true; } } // Only show success message if changes were actually made and no errors occurred if ($changesMade && ! $errorOccurred) { $this->dispatch('success', 'Environment variables updated.'); } } private function handleSingleSubmit($data) { $found = $this->resource->environment_variables()->where('key', $data['key'])->first(); if ($found) { $this->dispatch('error', 'Environment variable already exists.'); return; } $maxOrder = $this->resource->environment_variables()->max('order') ?? 0; $environment = $this->createEnvironmentVariable($data); $environment->order = $maxOrder + 1; $environment->save(); // Clear computed property cache to force refresh unset($this->environmentVariables); unset($this->environmentVariablesPreview); $this->dispatch('success', 'Environment variable added.'); } private function createEnvironmentVariable($data) { $environment = new EnvironmentVariable; $environment->key = $data['key']; $environment->value = $data['value']; $environment->is_multiline = $data['is_multiline'] ?? false; $environment->is_literal = $data['is_literal'] ?? false; $environment->is_runtime = $data['is_runtime'] ?? true; $environment->is_buildtime = $data['is_buildtime'] ?? true; $environment->is_preview = $data['is_preview'] ?? false; $environment->comment = $data['comment'] ?? null; $environment->resourceable_id = $this->resource->id; $environment->resourceable_type = $this->resource->getMorphClass(); return $environment; } private function deleteRemovedVariables($isPreview, $variables) { $method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; // Get all environment variables that will be deleted $variablesToDelete = $this->resource->$method()->whereNotIn('key', array_keys($variables))->get(); // If there are no variables to delete, return 0 if ($variablesToDelete->isEmpty()) { return 0; } // Check if any of these variables are used in Docker Compose if ($this->resource->type() === 'service' || $this->resource->build_pack === 'dockercompose') { foreach ($variablesToDelete as $envVar) { [$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($envVar->key, $this->resource->docker_compose); if ($isUsed) { $this->dispatch('error', "Cannot delete environment variable '{$envVar->key}'

Please remove it from the Docker Compose file first."); return 0; } } } // If we get here, no variables are used in Docker Compose, so we can delete them $this->resource->$method()->whereNotIn('key', array_keys($variables))->delete(); return $variablesToDelete->count(); } private function updateOrCreateVariables($isPreview, $variables) { $count = 0; foreach ($variables as $key => $data) { if (str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL') || str($key)->startsWith('SERVICE_NAME')) { continue; } // Extract value and comment from parsed data // Handle both array format ['value' => ..., 'comment' => ...] and plain string values $value = is_array($data) ? ($data['value'] ?? '') : $data; $comment = is_array($data) ? ($data['comment'] ?? null) : null; $method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; $found = $this->resource->$method()->where('key', $key)->first(); if ($found) { if (! $found->is_shown_once && ! $found->is_multiline) { $changed = false; // Update value if it changed if ($found->value !== $value) { $found->value = $value; $changed = true; } // Only update comment from inline comment if one is provided (overwrites existing) // If $comment is null, don't touch existing comment field to preserve it if ($comment !== null && $found->comment !== $comment) { $found->comment = $comment; $changed = true; } if ($changed) { $found->save(); $count++; } } } else { $environment = new EnvironmentVariable; $environment->key = $key; $environment->value = $value; $environment->comment = $comment; // Set comment from inline comment $environment->is_multiline = false; $environment->is_preview = $isPreview; $environment->resourceable_id = $this->resource->id; $environment->resourceable_type = $this->resource->getMorphClass(); $environment->save(); $count++; } } return $count; } public function refreshEnvs() { $this->resource->refresh(); // Clear computed property cache to force refresh unset($this->environmentVariables); unset($this->environmentVariablesPreview); $this->getDevView(); } }