Merge remote-tracking branch 'origin/next' into fix/preview-deployments-invisible

This commit is contained in:
Andras Bacsai committed 2026-03-06 08:03:45 +01:00
commit 380a34c7d6
173 files changed
+10281 -1697

No files matched your search

+5 -1
View File
@@ -69,7 +69,11 @@ class Heading extends Component
public function mount()
{
$this->parameters = get_route_parameters();
$this->parameters = [
'project_uuid' => $this->database->environment->project->uuid,
'environment_uuid' => $this->database->environment->uuid,
'database_uuid' => $this->database->uuid,
];
}
public function stop()
+8 -2
View File
@@ -63,10 +63,16 @@ class DockerCompose extends Component
]);
$variables = parseEnvFormatToArray($this->envFile);
foreach ($variables as $key => $variable) {
foreach ($variables as $key => $data) {
// 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;
EnvironmentVariable::create([
'key' => $key,
'value' => $variable,
'value' => $value,
'comment' => $comment,
'is_preview' => false,
'resourceable_id' => $service->id,
'resourceable_type' => $service->getMorphClass(),
@@ -31,6 +31,8 @@ class Add extends Component
public bool $is_buildtime = true;
public ?string $comment = null;
public array $problematicVariables = [];
protected $listeners = ['clearAddEnv' => 'clear'];
@@ -42,6 +44,7 @@ class Add extends Component
'is_literal' => 'required|boolean',
'is_runtime' => 'required|boolean',
'is_buildtime' => 'required|boolean',
'comment' => 'nullable|string|max:256',
];
protected $validationAttributes = [
@@ -51,6 +54,7 @@ class Add extends Component
'is_literal' => 'literal',
'is_runtime' => 'runtime',
'is_buildtime' => 'buildtime',
'comment' => 'comment',
];
public function mount()
@@ -136,6 +140,7 @@ class Add extends Component
'is_runtime' => $this->is_runtime,
'is_buildtime' => $this->is_buildtime,
'is_preview' => $this->is_preview,
'comment' => $this->comment,
]);
$this->clear();
}
@@ -148,5 +153,6 @@ class Add extends Component
$this->is_literal = false;
$this->is_runtime = true;
$this->is_buildtime = true;
$this->comment = null;
}
}
@@ -89,6 +89,62 @@ class All extends Component
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);
@@ -240,6 +296,7 @@ class All extends Component
$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();
@@ -280,18 +337,37 @@ class All extends Component
private function updateOrCreateVariables($isPreview, $variables)
{
$count = 0;
foreach ($variables as $key => $value) {
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) {
// Only count as a change if the value actually changed
$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++;
}
@@ -300,6 +376,7 @@ class All extends Component
$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;
@@ -24,6 +24,8 @@ class Show extends Component
public bool $isLocked = false;
public bool $isMagicVariable = false;
public bool $isSharedVariable = false;
public string $type;
@@ -34,6 +36,8 @@ class Show extends Component
public ?string $real_value = null;
public ?string $comment = null;
public bool $is_shared = false;
public bool $is_multiline = false;
@@ -63,6 +67,7 @@ class Show extends Component
protected $rules = [
'key' => 'required|string',
'value' => 'nullable',
'comment' => 'nullable|string|max:256',
'is_multiline' => 'required|boolean',
'is_literal' => 'required|boolean',
'is_shown_once' => 'required|boolean',
@@ -104,6 +109,7 @@ class Show extends Component
$this->validate([
'key' => 'required|string',
'value' => 'nullable',
'comment' => 'nullable|string|max:256',
'is_multiline' => 'required|boolean',
'is_literal' => 'required|boolean',
'is_shown_once' => 'required|boolean',
@@ -118,6 +124,7 @@ class Show extends Component
}
$this->env->key = $this->key;
$this->env->value = $this->value;
$this->env->comment = $this->comment;
$this->env->is_multiline = $this->is_multiline;
$this->env->is_literal = $this->is_literal;
$this->env->is_shown_once = $this->is_shown_once;
@@ -125,6 +132,7 @@ class Show extends Component
} else {
$this->key = $this->env->key;
$this->value = $this->env->value;
$this->comment = $this->env->comment;
$this->is_multiline = $this->env->is_multiline;
$this->is_literal = $this->env->is_literal;
$this->is_shown_once = $this->env->is_shown_once;
@@ -140,9 +148,13 @@ class Show extends Component
public function checkEnvs()
{
$this->isDisabled = false;
$this->isMagicVariable = false;
if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL') || str($this->env->key)->startsWith('SERVICE_NAME')) {
$this->isDisabled = true;
$this->isMagicVariable = true;
}
if ($this->env->is_shown_once) {
$this->isLocked = true;
}
@@ -0,0 +1,31 @@
<?php
namespace App\Livewire\Project\Shared\EnvironmentVariable;
use Livewire\Component;
class ShowHardcoded extends Component
{
public array $env;
public string $key;
public ?string $value = null;
public ?string $comment = null;
public ?string $serviceName = null;
public function mount()
{
$this->key = $this->env['key'];
$this->value = $this->env['value'] ?? null;
$this->comment = $this->env['comment'] ?? null;
$this->serviceName = $this->env['service_name'] ?? null;
}
public function render()
{
return view('livewire.project.shared.environment-variable.show-hardcoded');
}
}
+29 -10
View File
@@ -16,19 +16,25 @@ class HealthChecks extends Component
#[Validate(['boolean'])]
public bool $healthCheckEnabled = false;
#[Validate(['string'])]
#[Validate(['string', 'in:http,cmd'])]
public string $healthCheckType = 'http';
#[Validate(['nullable', 'required_if:healthCheckType,cmd', 'string', 'max:1000', 'regex:/^[a-zA-Z0-9 \-_.\/:=@,+]+$/'])]
public ?string $healthCheckCommand = null;
#[Validate(['required', 'string', 'in:GET,HEAD,POST,OPTIONS'])]
public string $healthCheckMethod;
#[Validate(['string'])]
#[Validate(['required', 'string', 'in:http,https'])]
public string $healthCheckScheme;
#[Validate(['string'])]
#[Validate(['required', 'string', 'regex:/^[a-zA-Z0-9.\-_]+$/'])]
public string $healthCheckHost;
#[Validate(['nullable', 'string'])]
#[Validate(['nullable', 'integer', 'min:1', 'max:65535'])]
public ?string $healthCheckPort = null;
#[Validate(['string'])]
#[Validate(['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%]+$#'])]
public string $healthCheckPath;
#[Validate(['integer'])]
@@ -54,12 +60,14 @@ class HealthChecks extends Component
protected $rules = [
'healthCheckEnabled' => 'boolean',
'healthCheckPath' => 'string',
'healthCheckPort' => 'nullable|string',
'healthCheckHost' => 'string',
'healthCheckMethod' => 'string',
'healthCheckType' => 'string|in:http,cmd',
'healthCheckCommand' => ['nullable', 'string', 'max:1000', 'regex:/^[a-zA-Z0-9 \-_.\/:=@,+]+$/'],
'healthCheckPath' => ['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%]+$#'],
'healthCheckPort' => 'nullable|integer|min:1|max:65535',
'healthCheckHost' => ['required', 'string', 'regex:/^[a-zA-Z0-9.\-_]+$/'],
'healthCheckMethod' => 'required|string|in:GET,HEAD,POST,OPTIONS',
'healthCheckReturnCode' => 'integer',
'healthCheckScheme' => 'string',
'healthCheckScheme' => 'required|string|in:http,https',
'healthCheckResponseText' => 'nullable|string',
'healthCheckInterval' => 'integer|min:1',
'healthCheckTimeout' => 'integer|min:1',
@@ -81,6 +89,8 @@ class HealthChecks extends Component
// Sync to model
$this->resource->health_check_enabled = $this->healthCheckEnabled;
$this->resource->health_check_type = $this->healthCheckType;
$this->resource->health_check_command = $this->healthCheckCommand;
$this->resource->health_check_method = $this->healthCheckMethod;
$this->resource->health_check_scheme = $this->healthCheckScheme;
$this->resource->health_check_host = $this->healthCheckHost;
@@ -98,6 +108,8 @@ class HealthChecks extends Component
} else {
// Sync from model
$this->healthCheckEnabled = $this->resource->health_check_enabled;
$this->healthCheckType = $this->resource->health_check_type ?? 'http';
$this->healthCheckCommand = $this->resource->health_check_command;
$this->healthCheckMethod = $this->resource->health_check_method;
$this->healthCheckScheme = $this->resource->health_check_scheme;
$this->healthCheckHost = $this->resource->health_check_host;
@@ -116,9 +128,12 @@ class HealthChecks extends Component
public function instantSave()
{
$this->authorize('update', $this->resource);
$this->validate();
// Sync component properties to model
$this->resource->health_check_enabled = $this->healthCheckEnabled;
$this->resource->health_check_type = $this->healthCheckType;
$this->resource->health_check_command = $this->healthCheckCommand;
$this->resource->health_check_method = $this->healthCheckMethod;
$this->resource->health_check_scheme = $this->healthCheckScheme;
$this->resource->health_check_host = $this->healthCheckHost;
@@ -143,6 +158,8 @@ class HealthChecks extends Component
// Sync component properties to model
$this->resource->health_check_enabled = $this->healthCheckEnabled;
$this->resource->health_check_type = $this->healthCheckType;
$this->resource->health_check_command = $this->healthCheckCommand;
$this->resource->health_check_method = $this->healthCheckMethod;
$this->resource->health_check_scheme = $this->healthCheckScheme;
$this->resource->health_check_host = $this->healthCheckHost;
@@ -171,6 +188,8 @@ class HealthChecks extends Component
// Sync component properties to model
$this->resource->health_check_enabled = $this->healthCheckEnabled;
$this->resource->health_check_type = $this->healthCheckType;
$this->resource->health_check_command = $this->healthCheckCommand;
$this->resource->health_check_method = $this->healthCheckMethod;
$this->resource->health_check_scheme = $this->healthCheckScheme;
$this->resource->health_check_host = $this->healthCheckHost;
@@ -49,9 +49,10 @@ class ResourceOperations extends Component
{
$this->authorize('update', $this->resource);
$new_destination = StandaloneDocker::find($destination_id);
$teamScope = fn ($q) => $q->where('team_id', currentTeam()->id);
$new_destination = StandaloneDocker::whereHas('server', $teamScope)->find($destination_id);
if (! $new_destination) {
$new_destination = SwarmDocker::find($destination_id);
$new_destination = SwarmDocker::whereHas('server', $teamScope)->find($destination_id);
}
if (! $new_destination) {
return $this->addError('destination_id', 'Destination not found.');
@@ -352,7 +353,7 @@ class ResourceOperations extends Component
{
try {
$this->authorize('update', $this->resource);
$new_environment = Environment::findOrFail($environment_id);
$new_environment = Environment::ownedByCurrentTeam()->findOrFail($environment_id);
$this->resource->update([
'environment_id' => $environment_id,
]);