Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions app/Livewire/Project/New/DockerCompose.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,16 @@ public function submit()
]);

$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(),
Expand Down
24 changes: 22 additions & 2 deletions app/Livewire/Project/Shared/EnvironmentVariable/All.php
Original file line number Diff line number Diff line change
Expand Up @@ -270,18 +270,37 @@ private function deleteRemovedVariables($isPreview, $variables)
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;
}

// Always update comment from inline comment (overwrites existing)
// Set to comment if provided, otherwise set to null if no comment
if ($found->comment !== $comment) {
$found->comment = $comment;
$changed = true;
}

if ($changed) {
$found->save();
$count++;
}
Expand All @@ -290,6 +309,7 @@ private function updateOrCreateVariables($isPreview, $variables)
$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;
Expand Down
6 changes: 6 additions & 0 deletions app/Livewire/Project/Shared/EnvironmentVariable/Show.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class Show extends Component

public ?string $real_value = null;

public ?string $comment = null;

public bool $is_shared = false;

public bool $is_multiline = false;
Expand Down Expand Up @@ -60,6 +62,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',
Expand Down Expand Up @@ -101,6 +104,7 @@ public function syncData(bool $toModel = false)
$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',
Expand All @@ -115,13 +119,15 @@ public function syncData(bool $toModel = false)
}
$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;
$this->env->save();
} 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;
Expand Down
2 changes: 2 additions & 0 deletions app/Models/EnvironmentVariable.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'key' => ['type' => 'string'],
'value' => ['type' => 'string'],
'real_value' => ['type' => 'string'],
'comment' => ['type' => 'string', 'nullable' => true],
'version' => ['type' => 'string'],
'created_at' => ['type' => 'string'],
'updated_at' => ['type' => 'string'],
Expand Down Expand Up @@ -65,6 +66,7 @@ protected static function booted()
'value' => $environment_variable->value,
'is_multiline' => $environment_variable->is_multiline ?? false,
'is_literal' => $environment_variable->is_literal ?? false,
'comment' => $environment_variable->comment,
'resourceable_type' => Application::class,
'resourceable_id' => $environment_variable->resourceable_id,
'is_preview' => true,
Expand Down
70 changes: 64 additions & 6 deletions bootstrap/helpers/shared.php
Original file line number Diff line number Diff line change
Expand Up @@ -423,13 +423,71 @@ function parseEnvFormatToArray($env_file_contents)
$equals_pos = strpos($line, '=');
if ($equals_pos !== false) {
$key = substr($line, 0, $equals_pos);
$value = substr($line, $equals_pos + 1);
if (substr($value, 0, 1) === '"' && substr($value, -1) === '"') {
$value = substr($value, 1, -1);
} elseif (substr($value, 0, 1) === "'" && substr($value, -1) === "'") {
$value = substr($value, 1, -1);
$value_and_comment = substr($line, $equals_pos + 1);
$comment = null;
$remainder = '';

// Check if value starts with quotes
$firstChar = isset($value_and_comment[0]) ? $value_and_comment[0] : '';
$isDoubleQuoted = $firstChar === '"';
$isSingleQuoted = $firstChar === "'";

if ($isDoubleQuoted) {
// Find the closing double quote
$closingPos = strpos($value_and_comment, '"', 1);
if ($closingPos !== false) {
// Extract quoted value and remove quotes
$value = substr($value_and_comment, 1, $closingPos - 1);
// Everything after closing quote (including comments)
$remainder = substr($value_and_comment, $closingPos + 1);
} else {
// No closing quote - treat as unquoted
$value = substr($value_and_comment, 1);
}
} elseif ($isSingleQuoted) {
// Find the closing single quote
$closingPos = strpos($value_and_comment, "'", 1);
if ($closingPos !== false) {
// Extract quoted value and remove quotes
$value = substr($value_and_comment, 1, $closingPos - 1);
// Everything after closing quote (including comments)
$remainder = substr($value_and_comment, $closingPos + 1);
} else {
// No closing quote - treat as unquoted
$value = substr($value_and_comment, 1);
}
} else {
// Unquoted value - strip inline comments
// Only treat # as comment if preceded by whitespace
if (preg_match('/\s+#/', $value_and_comment, $matches, PREG_OFFSET_CAPTURE)) {
// Found whitespace followed by #, extract comment
$remainder = substr($value_and_comment, $matches[0][1]);
$value = substr($value_and_comment, 0, $matches[0][1]);
$value = rtrim($value);
} else {
$value = $value_and_comment;
}
}
$env_array[$key] = $value;

// Extract comment from remainder (if any)
if ($remainder !== '') {
// Look for # in remainder
$hashPos = strpos($remainder, '#');
if ($hashPos !== false) {
// Extract everything after the # and trim
$comment = substr($remainder, $hashPos + 1);
$comment = trim($comment);
// Set to null if empty after trimming
if ($comment === '') {
$comment = null;
}
}
}

$env_array[$key] = [
'value' => $value,
'comment' => $comment,
];
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
*/
public function up(): void
{
Schema::create('cloud_init_scripts', function (Blueprint $table) {
$table->id();
$table->foreignId('team_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->text('script'); // Encrypted in the model
$table->timestamps();
Schema::create('cloud_init_scripts', function (Blueprint $table) {
$table->id();
$table->foreignId('team_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->text('script'); // Encrypted in the model
$table->timestamps();

$table->index('team_id');
});
$table->index('team_id');
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,29 @@
*/
public function up(): void
{
Schema::create('webhook_notification_settings', function (Blueprint $table) {
$table->id();
$table->foreignId('team_id')->constrained()->cascadeOnDelete();
Schema::create('webhook_notification_settings', function (Blueprint $table) {
$table->id();
$table->foreignId('team_id')->constrained()->cascadeOnDelete();

$table->boolean('webhook_enabled')->default(false);
$table->text('webhook_url')->nullable();
$table->boolean('webhook_enabled')->default(false);
$table->text('webhook_url')->nullable();

$table->boolean('deployment_success_webhook_notifications')->default(false);
$table->boolean('deployment_failure_webhook_notifications')->default(true);
$table->boolean('status_change_webhook_notifications')->default(false);
$table->boolean('backup_success_webhook_notifications')->default(false);
$table->boolean('backup_failure_webhook_notifications')->default(true);
$table->boolean('scheduled_task_success_webhook_notifications')->default(false);
$table->boolean('scheduled_task_failure_webhook_notifications')->default(true);
$table->boolean('docker_cleanup_success_webhook_notifications')->default(false);
$table->boolean('docker_cleanup_failure_webhook_notifications')->default(true);
$table->boolean('server_disk_usage_webhook_notifications')->default(true);
$table->boolean('server_reachable_webhook_notifications')->default(false);
$table->boolean('server_unreachable_webhook_notifications')->default(true);
$table->boolean('server_patch_webhook_notifications')->default(false);
$table->boolean('deployment_success_webhook_notifications')->default(false);
$table->boolean('deployment_failure_webhook_notifications')->default(true);
$table->boolean('status_change_webhook_notifications')->default(false);
$table->boolean('backup_success_webhook_notifications')->default(false);
$table->boolean('backup_failure_webhook_notifications')->default(true);
$table->boolean('scheduled_task_success_webhook_notifications')->default(false);
$table->boolean('scheduled_task_failure_webhook_notifications')->default(true);
$table->boolean('docker_cleanup_success_webhook_notifications')->default(false);
$table->boolean('docker_cleanup_failure_webhook_notifications')->default(true);
$table->boolean('server_disk_usage_webhook_notifications')->default(true);
$table->boolean('server_reachable_webhook_notifications')->default(false);
$table->boolean('server_unreachable_webhook_notifications')->default(true);
$table->boolean('server_patch_webhook_notifications')->default(false);

$table->unique(['team_id']);
});
$table->unique(['team_id']);
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->string('comment', 256)->nullable();
});

Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->string('comment', 256)->nullable();
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->dropColumn('comment');
});

Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->dropColumn('comment');
});
}
};
4 changes: 4 additions & 0 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9654,6 +9654,10 @@
"real_value": {
"type": "string"
},
"comment": {
"type": "string",
"nullable": true
},
"version": {
"type": "string"
},
Expand Down
3 changes: 3 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6197,6 +6197,9 @@ components:
type: string
real_value:
type: string
comment:
type: string
nullable: true
version:
type: string
created_at:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
<x-forms.button type="submit" @click="slideOverOpen=false">
Save
</x-forms.button>
</form>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@
@else
<form wire:submit.prevent='submit' class="flex flex-col gap-2">
@can('manageEnvironment', $resource)
<div class="flex items-center gap-2 p-3 mb-2 text-sm border rounded bg-warning/10 border-warning/50 dark:text-warning text-coollabs">
<svg class="w-5 h-5" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"/>
</svg>
<span><strong>Note:</strong> Inline comments with space before # (e.g., <code>KEY=value #comment</code>) are stripped. Values like <code>PASSWORD=pass#word</code> are preserved. Use the Comment field in Normal view to document variables.</span>
</div>

<x-forms.textarea rows="10" class="whitespace-pre-wrap" id="variables" wire:model="variables"
label="Production Environment Variables"></x-forms.textarea>

Expand Down
Loading