File upload restricted
+ @if($currentFile) +Current: {{ $currentFile }}
+ @endif +
+
diff --git a/app/Actions/Application/StopApplication.php b/app/Actions/Application/StopApplication.php
index 0ca703fce..ee3398b04 100644
--- a/app/Actions/Application/StopApplication.php
+++ b/app/Actions/Application/StopApplication.php
@@ -49,7 +49,7 @@ class StopApplication
}
if ($dockerCleanup) {
- CleanupDocker::dispatch($server, true);
+ CleanupDocker::dispatch($server, false, false);
}
} catch (\Exception $e) {
return $e->getMessage();
diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php
index a40eac17b..4314ccd2f 100644
--- a/app/Actions/Database/StartPostgresql.php
+++ b/app/Actions/Database/StartPostgresql.php
@@ -185,6 +185,8 @@ class StartPostgresql
}
}
+ $command = ['postgres'];
+
if (filled($this->database->postgres_conf)) {
$docker_compose['services'][$container_name]['volumes'] = array_merge(
$docker_compose['services'][$container_name]['volumes'],
@@ -195,29 +197,25 @@ class StartPostgresql
'read_only' => true,
]]
);
- $docker_compose['services'][$container_name]['command'] = [
- 'postgres',
- '-c',
- 'config_file=/etc/postgresql/postgresql.conf',
- ];
+ $command = array_merge($command, ['-c', 'config_file=/etc/postgresql/postgresql.conf']);
}
if ($this->database->enable_ssl) {
- $docker_compose['services'][$container_name]['command'] = [
- 'postgres',
- '-c',
- 'ssl=on',
- '-c',
- 'ssl_cert_file=/var/lib/postgresql/certs/server.crt',
- '-c',
- 'ssl_key_file=/var/lib/postgresql/certs/server.key',
- ];
+ $command = array_merge($command, [
+ '-c', 'ssl=on',
+ '-c', 'ssl_cert_file=/var/lib/postgresql/certs/server.crt',
+ '-c', 'ssl_key_file=/var/lib/postgresql/certs/server.key',
+ ]);
}
// Add custom docker run options
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
+ if (count($command) > 1) {
+ $docker_compose['services'][$container_name]['command'] = $command;
+ }
+
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php
index a03c9269e..5c881e743 100644
--- a/app/Actions/Database/StopDatabase.php
+++ b/app/Actions/Database/StopDatabase.php
@@ -18,7 +18,7 @@ class StopDatabase
{
use AsAction;
- public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $isDeleteOperation = false, bool $dockerCleanup = true)
+ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $dockerCleanup = true)
{
try {
$server = $database->destination->server;
@@ -29,7 +29,7 @@ class StopDatabase
$this->stopContainer($database, $database->uuid, 30);
if ($dockerCleanup) {
- CleanupDocker::dispatch($server, true);
+ CleanupDocker::dispatch($server, false, false);
}
if ($database->is_public) {
diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php
index d4b03ffc1..a06e547c5 100644
--- a/app/Actions/Proxy/CheckProxy.php
+++ b/app/Actions/Proxy/CheckProxy.php
@@ -66,7 +66,7 @@ class CheckProxy
if ($server->id === 0) {
$ip = 'host.docker.internal';
}
- $portsToCheck = ['80', '443'];
+ $portsToCheck = [];
try {
if ($server->proxyType() !== ProxyTypes::NONE->value) {
diff --git a/app/Actions/Server/CleanupDocker.php b/app/Actions/Server/CleanupDocker.php
index 754feecb1..392562167 100644
--- a/app/Actions/Server/CleanupDocker.php
+++ b/app/Actions/Server/CleanupDocker.php
@@ -11,7 +11,7 @@ class CleanupDocker
public string $jobQueue = 'high';
- public function handle(Server $server)
+ public function handle(Server $server, bool $deleteUnusedVolumes = false, bool $deleteUnusedNetworks = false)
{
$settings = instanceSettings();
$realtimeImage = config('constants.coolify.realtime_image');
@@ -36,11 +36,11 @@ class CleanupDocker
"docker images --filter before=$realtimeImageWithoutPrefixVersion --filter reference=$realtimeImageWithoutPrefix | grep $realtimeImageWithoutPrefix | awk '{print $3}' | xargs -r docker rmi -f",
];
- if ($server->settings->delete_unused_volumes) {
+ if ($deleteUnusedVolumes) {
$commands[] = 'docker volume prune -af';
}
- if ($server->settings->delete_unused_networks) {
+ if ($deleteUnusedNetworks) {
$commands[] = 'docker network prune -f';
}
diff --git a/app/Actions/Server/StartSentinel.php b/app/Actions/Server/StartSentinel.php
index 1ecf882dc..dd1a7ed53 100644
--- a/app/Actions/Server/StartSentinel.php
+++ b/app/Actions/Server/StartSentinel.php
@@ -2,6 +2,7 @@
namespace App\Actions\Server;
+use App\Events\SentinelRestarted;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -61,5 +62,8 @@ class StartSentinel
$server->settings->is_sentinel_enabled = true;
$server->settings->save();
$server->sentinelHeartbeat();
+
+ // Dispatch event to notify UI components
+ SentinelRestarted::dispatch($server, $version);
}
}
diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php
index 9a6cc140b..2a06428e2 100644
--- a/app/Actions/Server/UpdateCoolify.php
+++ b/app/Actions/Server/UpdateCoolify.php
@@ -29,7 +29,7 @@ class UpdateCoolify
if (! $this->server) {
return;
}
- CleanupDocker::dispatch($this->server);
+ CleanupDocker::dispatch($this->server, false, false);
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('constants.coolify.version');
if (! $manual_update) {
diff --git a/app/Actions/Service/DeleteService.php b/app/Actions/Service/DeleteService.php
index 404e11559..8790901cd 100644
--- a/app/Actions/Service/DeleteService.php
+++ b/app/Actions/Service/DeleteService.php
@@ -11,7 +11,7 @@ class DeleteService
{
use AsAction;
- public function handle(Service $service, bool $deleteConfigurations, bool $deleteVolumes, bool $dockerCleanup, bool $deleteConnectedNetworks)
+ public function handle(Service $service, bool $deleteVolumes, bool $deleteConnectedNetworks, bool $deleteConfigurations, bool $dockerCleanup)
{
try {
$server = data_get($service, 'server');
@@ -71,7 +71,7 @@ class DeleteService
$service->forceDelete();
if ($dockerCleanup) {
- CleanupDocker::dispatch($server, true);
+ CleanupDocker::dispatch($server, false, false);
}
}
}
diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php
index a7fa4b8b2..3f4e96479 100644
--- a/app/Actions/Service/StopService.php
+++ b/app/Actions/Service/StopService.php
@@ -14,7 +14,7 @@ class StopService
public string $jobQueue = 'high';
- public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true)
+ public function handle(Service $service, bool $deleteConnectedNetworks = false, bool $dockerCleanup = true)
{
try {
$server = $service->destination->server;
@@ -36,11 +36,11 @@ class StopService
$this->stopContainersInParallel($containersToStop, $server);
}
- if ($isDeleteOperation) {
+ if ($deleteConnectedNetworks) {
$service->deleteConnectedNetworks();
}
if ($dockerCleanup) {
- CleanupDocker::dispatch($server, true);
+ CleanupDocker::dispatch($server, false, false);
}
} catch (\Exception $e) {
return $e->getMessage();
diff --git a/app/Console/Commands/CleanupNames.php b/app/Console/Commands/CleanupNames.php
new file mode 100644
index 000000000..2992e32b9
--- /dev/null
+++ b/app/Console/Commands/CleanupNames.php
@@ -0,0 +1,248 @@
+ Project::class,
+ 'Environment' => Environment::class,
+ 'Application' => Application::class,
+ 'Service' => Service::class,
+ 'Server' => Server::class,
+ 'Team' => Team::class,
+ 'StandalonePostgresql' => StandalonePostgresql::class,
+ 'StandaloneMysql' => StandaloneMysql::class,
+ 'StandaloneRedis' => StandaloneRedis::class,
+ 'StandaloneMongodb' => StandaloneMongodb::class,
+ 'StandaloneMariadb' => StandaloneMariadb::class,
+ 'StandaloneKeydb' => StandaloneKeydb::class,
+ 'StandaloneDragonfly' => StandaloneDragonfly::class,
+ 'StandaloneClickhouse' => StandaloneClickhouse::class,
+ 'S3Storage' => S3Storage::class,
+ 'Tag' => Tag::class,
+ 'PrivateKey' => PrivateKey::class,
+ 'ScheduledTask' => ScheduledTask::class,
+ ];
+
+ protected array $changes = [];
+
+ protected int $totalProcessed = 0;
+
+ protected int $totalCleaned = 0;
+
+ public function handle(): int
+ {
+ $this->info('🔍 Scanning for invalid characters in name fields...');
+
+ if ($this->option('backup') && ! $this->option('dry-run')) {
+ $this->createBackup();
+ }
+
+ $modelFilter = $this->option('model');
+ $modelsToProcess = $modelFilter
+ ? [$modelFilter => $this->modelsToClean[$modelFilter] ?? null]
+ : $this->modelsToClean;
+
+ if ($modelFilter && ! isset($this->modelsToClean[$modelFilter])) {
+ $this->error("❌ Unknown model: {$modelFilter}");
+ $this->info('Available models: '.implode(', ', array_keys($this->modelsToClean)));
+
+ return self::FAILURE;
+ }
+
+ foreach ($modelsToProcess as $modelName => $modelClass) {
+ if (! $modelClass) {
+ continue;
+ }
+ $this->processModel($modelName, $modelClass);
+ }
+
+ $this->displaySummary();
+
+ if (! $this->option('dry-run') && $this->totalCleaned > 0) {
+ $this->logChanges();
+ }
+
+ return self::SUCCESS;
+ }
+
+ protected function processModel(string $modelName, string $modelClass): void
+ {
+ $this->info("\n📋 Processing {$modelName}...");
+
+ try {
+ $records = $modelClass::all(['id', 'name']);
+ $cleaned = 0;
+
+ foreach ($records as $record) {
+ $this->totalProcessed++;
+
+ $originalName = $record->name;
+ $sanitizedName = $this->sanitizeName($originalName);
+
+ if ($sanitizedName !== $originalName) {
+ $this->changes[] = [
+ 'model' => $modelName,
+ 'id' => $record->id,
+ 'original' => $originalName,
+ 'sanitized' => $sanitizedName,
+ 'timestamp' => now(),
+ ];
+
+ if (! $this->option('dry-run')) {
+ // Update without triggering events/mutators to avoid conflicts
+ $modelClass::where('id', $record->id)->update(['name' => $sanitizedName]);
+ }
+
+ $cleaned++;
+ $this->totalCleaned++;
+
+ $this->warn(" 🧹 {$modelName} #{$record->id}:");
+ $this->line(' From: '.$this->truncate($originalName, 80));
+ $this->line(' To: '.$this->truncate($sanitizedName, 80));
+ }
+ }
+
+ if ($cleaned > 0) {
+ $action = $this->option('dry-run') ? 'would be sanitized' : 'sanitized';
+ $this->info(" ✅ {$cleaned}/{$records->count()} records {$action}");
+ } else {
+ $this->info(' ✨ No invalid characters found');
+ }
+
+ } catch (\Exception $e) {
+ $this->error(" ❌ Error processing {$modelName}: ".$e->getMessage());
+ }
+ }
+
+ protected function sanitizeName(string $name): string
+ {
+ // Remove all characters that don't match the allowed pattern
+ // Use the shared ValidationPatterns to ensure consistency
+ $allowedPattern = str_replace(['/', '^', '$'], '', ValidationPatterns::NAME_PATTERN);
+ $sanitized = preg_replace('/[^'.$allowedPattern.']+/', '', $name);
+
+ // Clean up excessive whitespace but preserve other allowed characters
+ $sanitized = preg_replace('/\s+/', ' ', $sanitized);
+ $sanitized = trim($sanitized);
+
+ // If result is empty, provide a default name
+ if (empty($sanitized)) {
+ $sanitized = 'sanitized-item';
+ }
+
+ return $sanitized;
+ }
+
+ protected function displaySummary(): void
+ {
+ $this->info("\n".str_repeat('=', 60));
+ $this->info('📊 CLEANUP SUMMARY');
+ $this->info(str_repeat('=', 60));
+
+ $this->line("Records processed: {$this->totalProcessed}");
+ $this->line("Records with invalid characters: {$this->totalCleaned}");
+
+ if ($this->option('dry-run')) {
+ $this->warn("\n🔍 DRY RUN - No changes were made to the database");
+ $this->info('Run without --dry-run to apply these changes');
+ } else {
+ if ($this->totalCleaned > 0) {
+ $this->info("\n✅ Database successfully sanitized!");
+ $this->info('Changes logged to storage/logs/name-cleanup.log');
+ } else {
+ $this->info("\n✨ No cleanup needed - all names are valid!");
+ }
+ }
+ }
+
+ protected function logChanges(): void
+ {
+ $logFile = storage_path('logs/name-cleanup.log');
+ $logData = [
+ 'timestamp' => now()->toISOString(),
+ 'total_processed' => $this->totalProcessed,
+ 'total_cleaned' => $this->totalCleaned,
+ 'changes' => $this->changes,
+ ];
+
+ file_put_contents($logFile, json_encode($logData, JSON_PRETTY_PRINT)."\n", FILE_APPEND);
+
+ Log::info('Name Sanitization completed', [
+ 'total_processed' => $this->totalProcessed,
+ 'total_sanitized' => $this->totalCleaned,
+ 'changes_count' => count($this->changes),
+ ]);
+ }
+
+ protected function createBackup(): void
+ {
+ $this->info('💾 Creating database backup...');
+
+ try {
+ $backupFile = storage_path('backups/name-cleanup-backup-'.now()->format('Y-m-d-H-i-s').'.sql');
+
+ // Ensure backup directory exists
+ if (! file_exists(dirname($backupFile))) {
+ mkdir(dirname($backupFile), 0755, true);
+ }
+
+ $dbConfig = config('database.connections.'.config('database.default'));
+ $command = sprintf(
+ 'pg_dump -h %s -p %s -U %s -d %s > %s',
+ $dbConfig['host'],
+ $dbConfig['port'],
+ $dbConfig['username'],
+ $dbConfig['database'],
+ $backupFile
+ );
+
+ exec($command, $output, $returnCode);
+
+ if ($returnCode === 0) {
+ $this->info("✅ Backup created: {$backupFile}");
+ } else {
+ $this->warn('⚠️ Backup creation may have failed. Proceeding anyway...');
+ }
+ } catch (\Exception $e) {
+ $this->warn('⚠️ Could not create backup: '.$e->getMessage());
+ $this->warn('Proceeding without backup...');
+ }
+ }
+
+ protected function truncate(string $text, int $length): string
+ {
+ return strlen($text) > $length ? substr($text, 0, $length).'...' : $text;
+ }
+}
diff --git a/app/Console/Commands/Generate/Services.php b/app/Console/Commands/Generate/Services.php
index 577e94ac8..42f9360bb 100644
--- a/app/Console/Commands/Generate/Services.php
+++ b/app/Console/Commands/Generate/Services.php
@@ -16,7 +16,7 @@ class Services extends Command
/**
* {@inheritdoc}
*/
- protected $description = 'Generate service-templates.yaml based on /templates/compose directory';
+ protected $description = 'Generates service-templates json file based on /templates/compose directory';
public function handle(): int
{
@@ -33,7 +33,10 @@ class Services extends Command
];
})->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
- file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson.PHP_EOL);
+ file_put_contents(base_path('templates/'.config('constants.services.file_name')), $serviceTemplatesJson.PHP_EOL);
+
+ // Generate service-templates.json with SERVICE_URL changed to SERVICE_FQDN
+ $this->generateServiceTemplatesWithFqdn();
return self::SUCCESS;
}
@@ -71,6 +74,7 @@ class Services extends Command
'slogan' => $data->get('slogan', str($file)->headline()),
'compose' => $compose,
'tags' => $tags,
+ 'category' => $data->get('category'),
'logo' => $data->get('logo', 'svgs/default.webp'),
'minversion' => $data->get('minversion', '0.0.0'),
];
@@ -86,4 +90,145 @@ class Services extends Command
return $payload;
}
+
+ private function generateServiceTemplatesWithFqdn(): void
+ {
+ $serviceTemplatesWithFqdn = collect(array_merge(
+ glob(base_path('templates/compose/*.yaml')),
+ glob(base_path('templates/compose/*.yml'))
+ ))
+ ->mapWithKeys(function ($file): array {
+ $file = basename($file);
+ $parsed = $this->processFileWithFqdn($file);
+
+ return $parsed === false ? [] : [
+ Arr::pull($parsed, 'name') => $parsed,
+ ];
+ })->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+
+ file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesWithFqdn.PHP_EOL);
+
+ // Generate service-templates-raw.json with non-base64 encoded compose content
+ // $this->generateServiceTemplatesRaw();
+ }
+
+ private function processFileWithFqdn(string $file): false|array
+ {
+ $content = file_get_contents(base_path("templates/compose/$file"));
+
+ $data = collect(explode(PHP_EOL, $content))->mapWithKeys(function ($line): array {
+ preg_match('/^#(?]*>/', '
', $html); + + // Lists + $html = preg_replace('/
]*>/', '', $html);
+ $html = preg_replace('/]*>/', '', $html);
+
+ // Links - Apply styling to existing markdown links
+ $html = preg_replace('/]*)>/', '', $html);
+
+ // Convert plain URLs to clickable links (that aren't already in tags)
+ $html = preg_replace('/(?)(?"]+)(?![^<]*<\/a>)/', '$1', $html);
+
+ // Strong/bold text
+ $html = preg_replace('/]*>/', '', $html);
+
+ // Emphasis/italic text
+ $html = preg_replace('/]*>/', '', $html);
+
+ return $html;
+ }
+}
diff --git a/app/Support/ValidationPatterns.php b/app/Support/ValidationPatterns.php
new file mode 100644
index 000000000..965142558
--- /dev/null
+++ b/app/Support/ValidationPatterns.php
@@ -0,0 +1,93 @@
+ 'The name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
+ 'name.min' => 'The name must be at least :min characters.',
+ 'name.max' => 'The name may not be greater than :max characters.',
+ ];
+ }
+
+ /**
+ * Get validation messages for description fields
+ */
+ public static function descriptionMessages(): array
+ {
+ return [
+ 'description.regex' => 'The description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
+ 'description.max' => 'The description may not be greater than :max characters.',
+ ];
+ }
+
+ /**
+ * Get combined validation messages for both name and description fields
+ */
+ public static function combinedMessages(): array
+ {
+ return array_merge(self::nameMessages(), self::descriptionMessages());
+ }
+}
diff --git a/app/Traits/AuthorizesResourceCreation.php b/app/Traits/AuthorizesResourceCreation.php
new file mode 100644
index 000000000..01ae7c8d9
--- /dev/null
+++ b/app/Traits/AuthorizesResourceCreation.php
@@ -0,0 +1,20 @@
+authorize('createAnyResource');
+ }
+}
diff --git a/app/Traits/HasSafeStringAttribute.php b/app/Traits/HasSafeStringAttribute.php
new file mode 100644
index 000000000..8a5d2ce77
--- /dev/null
+++ b/app/Traits/HasSafeStringAttribute.php
@@ -0,0 +1,25 @@
+attributes['name'] = $this->customizeName($sanitized);
+ }
+
+ protected function customizeName($value)
+ {
+ return $value; // Default: no customization
+ }
+
+ public function setDescriptionAttribute($value)
+ {
+ $this->attributes['description'] = strip_tags($value);
+ }
+}
diff --git a/app/View/Components/Forms/Button.php b/app/View/Components/Forms/Button.php
index bf88d3f88..b54444261 100644
--- a/app/View/Components/Forms/Button.php
+++ b/app/View/Components/Forms/Button.php
@@ -4,6 +4,7 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
+use Illuminate\Support\Facades\Gate;
use Illuminate\View\Component;
class Button extends Component
@@ -17,7 +18,19 @@ class Button extends Component
public ?string $modalId = null,
public string $defaultClass = 'button',
public bool $showLoadingIndicator = true,
+ public ?string $canGate = null,
+ public mixed $canResource = null,
+ public bool $autoDisable = true,
) {
+ // Handle authorization-based disabling
+ if ($this->canGate && $this->canResource && $this->autoDisable) {
+ $hasPermission = Gate::allows($this->canGate, $this->canResource);
+
+ if (! $hasPermission) {
+ $this->disabled = true;
+ }
+ }
+
if ($this->noStyle) {
$this->defaultClass = '';
}
diff --git a/app/View/Components/Forms/Checkbox.php b/app/View/Components/Forms/Checkbox.php
index 8db739642..ece7f0e35 100644
--- a/app/View/Components/Forms/Checkbox.php
+++ b/app/View/Components/Forms/Checkbox.php
@@ -4,6 +4,7 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
+use Illuminate\Support\Facades\Gate;
use Illuminate\View\Component;
class Checkbox extends Component
@@ -22,7 +23,20 @@ class Checkbox extends Component
public string|bool $instantSave = false,
public bool $disabled = false,
public string $defaultClass = 'dark:border-neutral-700 text-coolgray-400 focus:ring-warning dark:bg-coolgray-100 rounded-sm cursor-pointer dark:disabled:bg-base dark:disabled:cursor-not-allowed',
+ public ?string $canGate = null,
+ public mixed $canResource = null,
+ public bool $autoDisable = true,
) {
+ // Handle authorization-based disabling
+ if ($this->canGate && $this->canResource && $this->autoDisable) {
+ $hasPermission = Gate::allows($this->canGate, $this->canResource);
+
+ if (! $hasPermission) {
+ $this->disabled = true;
+ $this->instantSave = false; // Disable instant save for unauthorized users
+ }
+ }
+
if ($this->disabled) {
$this->defaultClass .= ' opacity-40';
}
diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php
index 7283ef20f..83c98c0df 100644
--- a/app/View/Components/Forms/Input.php
+++ b/app/View/Components/Forms/Input.php
@@ -4,6 +4,7 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
+use Illuminate\Support\Facades\Gate;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
@@ -25,7 +26,20 @@ class Input extends Component
public string $autocomplete = 'off',
public ?int $minlength = null,
public ?int $maxlength = null,
- ) {}
+ public bool $autofocus = false,
+ public ?string $canGate = null,
+ public mixed $canResource = null,
+ public bool $autoDisable = true,
+ ) {
+ // Handle authorization-based disabling
+ if ($this->canGate && $this->canResource && $this->autoDisable) {
+ $hasPermission = Gate::allows($this->canGate, $this->canResource);
+
+ if (! $hasPermission) {
+ $this->disabled = true;
+ }
+ }
+ }
public function render(): View|Closure|string
{
diff --git a/app/View/Components/Forms/Select.php b/app/View/Components/Forms/Select.php
index feb4bf343..49b69136b 100644
--- a/app/View/Components/Forms/Select.php
+++ b/app/View/Components/Forms/Select.php
@@ -4,6 +4,7 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
+use Illuminate\Support\Facades\Gate;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
@@ -19,9 +20,19 @@ class Select extends Component
public ?string $helper = null,
public bool $required = false,
public bool $disabled = false,
- public string $defaultClass = 'select w-full'
+ public string $defaultClass = 'select w-full',
+ public ?string $canGate = null,
+ public mixed $canResource = null,
+ public bool $autoDisable = true,
) {
- //
+ // Handle authorization-based disabling
+ if ($this->canGate && $this->canResource && $this->autoDisable) {
+ $hasPermission = Gate::allows($this->canGate, $this->canResource);
+
+ if (! $hasPermission) {
+ $this->disabled = true;
+ }
+ }
}
/**
diff --git a/app/View/Components/Forms/Textarea.php b/app/View/Components/Forms/Textarea.php
index 6081c2a8a..3148d2566 100644
--- a/app/View/Components/Forms/Textarea.php
+++ b/app/View/Components/Forms/Textarea.php
@@ -4,6 +4,7 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
+use Illuminate\Support\Facades\Gate;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
@@ -33,8 +34,18 @@ class Textarea extends Component
public string $defaultClassInput = 'input',
public ?int $minlength = null,
public ?int $maxlength = null,
+ public ?string $canGate = null,
+ public mixed $canResource = null,
+ public bool $autoDisable = true,
) {
- //
+ // Handle authorization-based disabling
+ if ($this->canGate && $this->canResource && $this->autoDisable) {
+ $hasPermission = Gate::allows($this->canGate, $this->canResource);
+
+ if (! $hasPermission) {
+ $this->disabled = true;
+ }
+ }
}
/**
diff --git a/backlog/config.yml b/backlog/config.yml
new file mode 100644
index 000000000..42af39aa7
--- /dev/null
+++ b/backlog/config.yml
@@ -0,0 +1,16 @@
+project_name: "Coolify"
+default_status: "To Do"
+statuses: ["To Do", "In Progress", "Done"]
+labels: []
+milestones: []
+date_format: yyyy-mm-dd
+max_column_width: 20
+default_editor: "vim"
+auto_open_browser: true
+default_port: 6420
+remote_operations: true
+auto_commit: false
+zero_padded_ids: 5
+bypass_git_hooks: true
+check_active_branches: true
+active_branch_days: 30
diff --git a/backlog/tasks/task-00001 - Implement-Docker-build-caching-for-Coolify-staging-builds.md b/backlog/tasks/task-00001 - Implement-Docker-build-caching-for-Coolify-staging-builds.md
new file mode 100644
index 000000000..13a0a9c94
--- /dev/null
+++ b/backlog/tasks/task-00001 - Implement-Docker-build-caching-for-Coolify-staging-builds.md
@@ -0,0 +1,58 @@
+---
+id: task-00001
+title: Implement Docker build caching for Coolify staging builds
+status: To Do
+assignee: []
+created_date: '2025-08-26 12:15'
+updated_date: '2025-08-26 12:16'
+labels:
+ - heyandras
+ - performance
+ - docker
+ - ci-cd
+ - build-optimization
+dependencies: []
+priority: high
+---
+
+## Description
+
+Implement comprehensive Docker build caching to reduce staging build times by 50-70% through BuildKit cache mounts for dependencies and GitHub Actions registry caching. This optimization will significantly reduce build times from ~10-15 minutes to ~3-5 minutes, decrease network usage, and lower GitHub Actions costs.
+
+## Acceptance Criteria
+
+- [ ] #1 Docker BuildKit cache mounts are added to Composer dependency installation in production Dockerfile
+- [ ] #2 Docker BuildKit cache mounts are added to NPM dependency installation in production Dockerfile
+- [ ] #3 GitHub Actions BuildX setup is configured for both AMD64 and AARCH64 jobs
+- [ ] #4 Registry cache-from and cache-to configurations are implemented for both architecture builds
+- [ ] #5 Build time reduction of at least 40% is achieved in staging builds
+- [ ] #6 GitHub Actions minutes consumption is reduced compared to baseline
+- [ ] #7 All existing build functionality remains intact with no regressions
+
+
+## Implementation Plan
+
+1. Modify docker/production/Dockerfile to add BuildKit cache mounts:
+ - Add cache mount for Composer dependencies at line 30: --mount=type=cache,target=/var/www/.composer/cache
+ - Add cache mount for NPM dependencies at line 41: --mount=type=cache,target=/root/.npm
+
+2. Update .github/workflows/coolify-staging-build.yml for AMD64 job:
+ - Add docker/setup-buildx-action@v3 step after checkout
+ - Configure cache-from and cache-to parameters in build-push-action
+ - Use registry caching with buildcache-amd64 tags
+
+3. Update .github/workflows/coolify-staging-build.yml for AARCH64 job:
+ - Add docker/setup-buildx-action@v3 step after checkout
+ - Configure cache-from and cache-to parameters in build-push-action
+ - Use registry caching with buildcache-aarch64 tags
+
+4. Test implementation:
+ - Measure baseline build times before changes
+ - Deploy changes and monitor initial build (will be slower due to cache population)
+ - Measure subsequent build times to verify 40%+ improvement
+ - Validate all build outputs and functionality remain unchanged
+
+5. Monitor and validate:
+ - Track GitHub Actions minutes consumption reduction
+ - Ensure Docker registry storage usage is reasonable
+ - Verify no build failures or regressions introduced
diff --git a/backlog/tasks/task-00001.01 - Add-BuildKit-cache-mounts-to-Dockerfile.md b/backlog/tasks/task-00001.01 - Add-BuildKit-cache-mounts-to-Dockerfile.md
new file mode 100644
index 000000000..93fa3e431
--- /dev/null
+++ b/backlog/tasks/task-00001.01 - Add-BuildKit-cache-mounts-to-Dockerfile.md
@@ -0,0 +1,24 @@
+---
+id: task-00001.01
+title: Add BuildKit cache mounts to Dockerfile
+status: To Do
+assignee: []
+created_date: '2025-08-26 12:19'
+labels:
+ - docker
+ - buildkit
+ - performance
+ - dockerfile
+dependencies: []
+parent_task_id: task-00001
+priority: high
+---
+
+## Description
+
+Modify the production Dockerfile to include BuildKit cache mounts for Composer and NPM dependencies to speed up subsequent builds by reusing cached dependency installations
+
+## Acceptance Criteria
+
+- [ ] #1 Cache mount for Composer dependencies is added at line 30 with --mount=type=cache target=/var/www/.composer/cache,Cache mount for NPM dependencies is added at line 41 with --mount=type=cache target=/root/.npm,Dockerfile syntax remains valid and builds successfully,All existing functionality is preserved with no regressions
+
diff --git a/backlog/tasks/task-00001.02 - Configure-BuildX-and-registry-caching-for-AMD64-staging-builds.md b/backlog/tasks/task-00001.02 - Configure-BuildX-and-registry-caching-for-AMD64-staging-builds.md
new file mode 100644
index 000000000..60ac514f6
--- /dev/null
+++ b/backlog/tasks/task-00001.02 - Configure-BuildX-and-registry-caching-for-AMD64-staging-builds.md
@@ -0,0 +1,24 @@
+---
+id: task-00001.02
+title: Configure BuildX and registry caching for AMD64 staging builds
+status: To Do
+assignee: []
+created_date: '2025-08-26 12:19'
+labels:
+ - github-actions
+ - buildx
+ - caching
+ - amd64
+dependencies: []
+parent_task_id: task-00001
+priority: high
+---
+
+## Description
+
+Update the GitHub Actions workflow to add BuildX setup and configure registry-based caching for the AMD64 build job to leverage Docker layer caching across builds
+
+## Acceptance Criteria
+
+- [ ] #1 docker/setup-buildx-action@v3 step is added after checkout in AMD64 job,Registry cache configuration is added to build-push-action with cache-from and cache-to parameters,Cache tags use buildcache-amd64 naming convention for architecture-specific caching,Build job runs successfully with caching enabled,No impact on existing build outputs or functionality
+
diff --git a/backlog/tasks/task-00001.03 - Configure-BuildX-and-registry-caching-for-AARCH64-staging-builds.md b/backlog/tasks/task-00001.03 - Configure-BuildX-and-registry-caching-for-AARCH64-staging-builds.md
new file mode 100644
index 000000000..3dd730d34
--- /dev/null
+++ b/backlog/tasks/task-00001.03 - Configure-BuildX-and-registry-caching-for-AARCH64-staging-builds.md
@@ -0,0 +1,25 @@
+---
+id: task-00001.03
+title: Configure BuildX and registry caching for AARCH64 staging builds
+status: To Do
+assignee: []
+created_date: '2025-08-26 12:19'
+labels:
+ - github-actions
+ - buildx
+ - caching
+ - aarch64
+ - self-hosted
+dependencies: []
+parent_task_id: task-00001
+priority: high
+---
+
+## Description
+
+Update the GitHub Actions workflow to add BuildX setup and configure registry-based caching for the AARCH64 build job running on self-hosted ARM64 runners
+
+## Acceptance Criteria
+
+- [ ] #1 docker/setup-buildx-action@v3 step is added after checkout in AARCH64 job,Registry cache configuration is added to build-push-action with cache-from and cache-to parameters,Cache tags use buildcache-aarch64 naming convention for architecture-specific caching,Build job runs successfully on self-hosted ARM64 runner with caching enabled,No impact on existing build outputs or functionality
+
diff --git a/backlog/tasks/task-00001.04 - Establish-build-time-baseline-measurements.md b/backlog/tasks/task-00001.04 - Establish-build-time-baseline-measurements.md
new file mode 100644
index 000000000..6fa997663
--- /dev/null
+++ b/backlog/tasks/task-00001.04 - Establish-build-time-baseline-measurements.md
@@ -0,0 +1,24 @@
+---
+id: task-00001.04
+title: Establish build time baseline measurements
+status: To Do
+assignee: []
+created_date: '2025-08-26 12:19'
+labels:
+ - performance
+ - testing
+ - baseline
+ - measurement
+dependencies: []
+parent_task_id: task-00001
+priority: medium
+---
+
+## Description
+
+Measure and document current staging build times for both AMD64 and AARCH64 architectures before implementing caching optimizations to establish a performance baseline for comparison
+
+## Acceptance Criteria
+
+- [ ] #1 Baseline build times are measured for at least 3 consecutive AMD64 builds,Baseline build times are measured for at least 3 consecutive AARCH64 builds,Average build time and GitHub Actions minutes consumption are documented,Baseline measurements include both cold builds and any existing warm builds,Results are documented in a format suitable for comparing against post-optimization builds
+
diff --git a/backlog/tasks/task-00001.05 - Validate-caching-implementation-and-measure-performance-improvements.md b/backlog/tasks/task-00001.05 - Validate-caching-implementation-and-measure-performance-improvements.md
new file mode 100644
index 000000000..6a11168da
--- /dev/null
+++ b/backlog/tasks/task-00001.05 - Validate-caching-implementation-and-measure-performance-improvements.md
@@ -0,0 +1,28 @@
+---
+id: task-00001.05
+title: Validate caching implementation and measure performance improvements
+status: To Do
+assignee: []
+created_date: '2025-08-26 12:19'
+labels:
+ - testing
+ - performance
+ - validation
+ - measurement
+dependencies:
+ - task-00001.01
+ - task-00001.02
+ - task-00001.03
+ - task-00001.04
+parent_task_id: task-00001
+priority: high
+---
+
+## Description
+
+Test the complete Docker build caching implementation by running multiple staging builds and measuring performance improvements to ensure the 40% build time reduction target is achieved
+
+## Acceptance Criteria
+
+- [ ] #1 First build after cache implementation runs successfully (expected slower due to cache population),Second and subsequent builds show significant time reduction compared to baseline,Build time reduction of at least 40% is achieved and documented,GitHub Actions minutes consumption is reduced compared to baseline measurements,All Docker images function identically to pre-optimization builds,No build failures or regressions are introduced by caching changes
+
diff --git a/backlog/tasks/task-00001.06 - Document-cache-optimization-results-and-create-production-workflow-plan.md b/backlog/tasks/task-00001.06 - Document-cache-optimization-results-and-create-production-workflow-plan.md
new file mode 100644
index 000000000..3749e58f3
--- /dev/null
+++ b/backlog/tasks/task-00001.06 - Document-cache-optimization-results-and-create-production-workflow-plan.md
@@ -0,0 +1,25 @@
+---
+id: task-00001.06
+title: Document cache optimization results and create production workflow plan
+status: To Do
+assignee: []
+created_date: '2025-08-26 12:19'
+labels:
+ - documentation
+ - planning
+ - production
+ - analysis
+dependencies:
+ - task-00001.05
+parent_task_id: task-00001
+priority: low
+---
+
+## Description
+
+Document the staging build caching results and create a detailed plan for applying the same optimizations to the production build workflow if staging results meet performance targets
+
+## Acceptance Criteria
+
+- [ ] #1 Performance improvement results are documented with before/after metrics,Cost savings in GitHub Actions minutes are calculated and documented,Analysis of Docker registry storage impact is provided,Detailed plan for production workflow optimization is created,Recommendations for cache retention policies and cleanup strategies are provided,Documentation includes rollback procedures if issues arise
+
diff --git a/backlog/tasks/task-00002 - Fix-Docker-cleanup-irregular-scheduling-in-cloud-environment.md b/backlog/tasks/task-00002 - Fix-Docker-cleanup-irregular-scheduling-in-cloud-environment.md
new file mode 100644
index 000000000..d0e63456b
--- /dev/null
+++ b/backlog/tasks/task-00002 - Fix-Docker-cleanup-irregular-scheduling-in-cloud-environment.md
@@ -0,0 +1,82 @@
+---
+id: task-00002
+title: Fix Docker cleanup irregular scheduling in cloud environment
+status: Done
+assignee:
+ - '@claude'
+created_date: '2025-08-26 12:17'
+updated_date: '2025-08-26 12:26'
+labels:
+ - backend
+ - performance
+ - cloud
+dependencies: []
+priority: high
+---
+
+## Description
+
+Docker cleanup jobs are running at irregular intervals instead of hourly as configured (0 * * * *) in the cloud environment with 2 Horizon workers and thousands of servers. The issue stems from the ServerManagerJob processing servers sequentially with a frozen execution time, causing timing mismatches when evaluating cron expressions for large server counts.
+
+## Acceptance Criteria
+
+- [x] #1 Docker cleanup runs consistently at the configured hourly intervals
+- [x] #2 All eligible servers receive cleanup jobs when due
+- [x] #3 Solution handles thousands of servers efficiently
+- [x] #4 Maintains backwards compatibility with existing settings
+- [x] #5 Cloud subscription checks are properly enforced
+
+
+## Implementation Plan
+
+1. Add processDockerCleanups() method to ScheduledJobManager
+ - Implement method to fetch all eligible servers
+ - Apply frozen execution time for consistent cron evaluation
+ - Check server functionality and cloud subscription status
+ - Dispatch DockerCleanupJob for servers where cleanup is due
+
+2. Implement helper methods in ScheduledJobManager
+ - getServersForCleanup(): Fetch servers with proper cloud/self-hosted filtering
+ - shouldProcessDockerCleanup(): Validate server eligibility
+ - Reuse existing shouldRunNow() method with frozen execution time
+
+3. Remove Docker cleanup logic from ServerManagerJob
+ - Delete lines 136-150 that handle Docker cleanup scheduling
+ - Keep other server management tasks intact
+
+4. Test the implementation
+ - Verify hourly execution with test servers
+ - Check timezone handling
+ - Validate cloud subscription filtering
+ - Monitor for duplicate job prevention via WithoutOverlapping middleware
+
+5. Deploy strategy
+ - First deploy updated ScheduledJobManager
+ - Monitor logs for successful hourly executions
+ - Once confirmed, remove cleanup from ServerManagerJob
+ - No database migrations required
+
+## Implementation Notes
+
+Successfully migrated Docker cleanup scheduling from ServerManagerJob to ScheduledJobManager.
+
+**Changes Made:**
+1. Added processDockerCleanups() method to ScheduledJobManager that processes all servers with a single frozen execution time
+2. Implemented getServersForCleanup() to fetch servers with proper cloud/self-hosted filtering
+3. Implemented shouldProcessDockerCleanup() for server eligibility validation
+4. Removed Docker cleanup logic from ServerManagerJob (lines 136-150)
+
+**Key Improvements:**
+- All servers now evaluated against the same timestamp, ensuring consistent hourly execution
+- Proper cloud subscription checks maintained
+- Backwards compatible - no database migrations or settings changes required
+- Follows the same proven pattern used for database backups
+
+**Files Modified:**
+- app/Jobs/ScheduledJobManager.php: Added Docker cleanup processing
+- app/Jobs/ServerManagerJob.php: Removed Docker cleanup logic
+
+**Testing:**
+- Syntax validation passed
+- Code formatting verified with Laravel Pint
+- PHPStan analysis completed (existing warnings unrelated to changes)
diff --git a/backlog/tasks/task-00003 - Simplify-resource-operations-UI-replace-boxes-with-dropdown-selections.md b/backlog/tasks/task-00003 - Simplify-resource-operations-UI-replace-boxes-with-dropdown-selections.md
new file mode 100644
index 000000000..38aa18209
--- /dev/null
+++ b/backlog/tasks/task-00003 - Simplify-resource-operations-UI-replace-boxes-with-dropdown-selections.md
@@ -0,0 +1,30 @@
+---
+id: task-00003
+title: Simplify resource operations UI - replace boxes with dropdown selections
+status: To Do
+assignee: []
+created_date: '2025-08-26 13:22'
+updated_date: '2025-08-26 13:22'
+labels:
+ - ui
+ - frontend
+ - livewire
+dependencies: []
+priority: medium
+---
+
+## Description
+
+Replace the current box-based layout in resource-operations.blade.php with clean dropdown selections to improve UX when there are many servers, projects, or environments. The current interface becomes overwhelming and cluttered with multiple modal confirmation boxes for each option.
+
+## Acceptance Criteria
+
+- [ ] #1 Clone section shows a dropdown to select server/destination instead of multiple boxes
+- [ ] #2 Move section shows a dropdown to select project/environment instead of multiple boxes
+- [ ] #3 Single "Clone Resource" button that triggers modal after dropdown selection
+- [ ] #4 Single "Move Resource" button that triggers modal after dropdown selection
+- [ ] #5 Authorization warnings remain in place for users without permissions
+- [ ] #6 All existing functionality preserved (cloning, moving, success messages)
+- [ ] #7 Clean, simple interface that scales well with many options
+- [ ] #8 Mobile-friendly dropdown interface
+
diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php
index 48962f89c..5dbd46b5e 100644
--- a/bootstrap/helpers/databases.php
+++ b/bootstrap/helpers/databases.php
@@ -237,11 +237,18 @@ function removeOldBackups($backup): void
{
try {
if ($backup->executions) {
- $localBackupsToDelete = deleteOldBackupsLocally($backup);
- if ($localBackupsToDelete->isNotEmpty()) {
+ // If local backup is disabled, mark all executions as having local storage deleted
+ if ($backup->disable_local_backup && $backup->save_s3) {
$backup->executions()
- ->whereIn('id', $localBackupsToDelete->pluck('id'))
+ ->where('local_storage_deleted', false)
->update(['local_storage_deleted' => true]);
+ } else {
+ $localBackupsToDelete = deleteOldBackupsLocally($backup);
+ if ($localBackupsToDelete->isNotEmpty()) {
+ $backup->executions()
+ ->whereIn('id', $localBackupsToDelete->pluck('id'))
+ ->update(['local_storage_deleted' => true]);
+ }
}
}
@@ -254,10 +261,18 @@ function removeOldBackups($backup): void
}
}
- $backup->executions()
- ->where('local_storage_deleted', true)
- ->where('s3_storage_deleted', true)
- ->delete();
+ // Delete executions where both local and S3 storage are marked as deleted
+ // or where only S3 is enabled and S3 storage is deleted
+ if ($backup->disable_local_backup && $backup->save_s3) {
+ $backup->executions()
+ ->where('s3_storage_deleted', true)
+ ->delete();
+ } else {
+ $backup->executions()
+ ->where('local_storage_deleted', true)
+ ->where('s3_storage_deleted', true)
+ ->delete();
+ }
} catch (\Exception $e) {
throw $e;
diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php
index 944c51e3c..1737ca714 100644
--- a/bootstrap/helpers/docker.php
+++ b/bootstrap/helpers/docker.php
@@ -256,12 +256,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
if (str($MINIO_BROWSER_REDIRECT_URL->value ?? '')->isEmpty()) {
$MINIO_BROWSER_REDIRECT_URL->update([
- 'value' => generateFqdn($server, 'console-'.$uuid, true),
+ 'value' => generateFqdn(server: $server, random: 'console-'.$uuid, parserVersion: $resource->compose_parsing_version, forceHttps: true),
]);
}
if (str($MINIO_SERVER_URL->value ?? '')->isEmpty()) {
$MINIO_SERVER_URL->update([
- 'value' => generateFqdn($server, 'minio-'.$uuid, true),
+ 'value' => generateFqdn(server: $server, random: 'minio-'.$uuid, parserVersion: $resource->compose_parsing_version, forceHttps: true),
]);
}
$payload = collect([
@@ -279,12 +279,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
if (str($LOGTO_ENDPOINT->value ?? '')->isEmpty()) {
$LOGTO_ENDPOINT->update([
- 'value' => generateFqdn($server, 'logto-'.$uuid),
+ 'value' => generateFqdn(server: $server, random: 'logto-'.$uuid, parserVersion: $resource->compose_parsing_version),
]);
}
if (str($LOGTO_ADMIN_ENDPOINT->value ?? '')->isEmpty()) {
$LOGTO_ADMIN_ENDPOINT->update([
- 'value' => generateFqdn($server, 'logto-admin-'.$uuid),
+ 'value' => generateFqdn(server: $server, random: 'logto-admin-'.$uuid, parserVersion: $resource->compose_parsing_version),
]);
}
$payload = collect([
@@ -1101,7 +1101,7 @@ function getContainerLogs(Server $server, string $container_id, int $lines = 100
], $server);
}
- $output .= removeAnsiColors($output);
+ $output = removeAnsiColors($output);
return $output;
}
diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php
new file mode 100644
index 000000000..f35e73390
--- /dev/null
+++ b/bootstrap/helpers/parsers.php
@@ -0,0 +1,1772 @@
+fileStorages();
+
+ try {
+ $yaml = Yaml::parse($compose);
+ } catch (\Exception) {
+ return collect([]);
+ }
+ $services = data_get($yaml, 'services', collect([]));
+ $topLevel = collect([
+ 'volumes' => collect(data_get($yaml, 'volumes', [])),
+ 'networks' => collect(data_get($yaml, 'networks', [])),
+ 'configs' => collect(data_get($yaml, 'configs', [])),
+ 'secrets' => collect(data_get($yaml, 'secrets', [])),
+ ]);
+ // If there are predefined volumes, make sure they are not null
+ if ($topLevel->get('volumes')->count() > 0) {
+ $temp = collect([]);
+ foreach ($topLevel['volumes'] as $volumeName => $volume) {
+ if (is_null($volume)) {
+ continue;
+ }
+ $temp->put($volumeName, $volume);
+ }
+ $topLevel['volumes'] = $temp;
+ }
+ // Get the base docker network
+ $baseNetwork = collect([$uuid]);
+ if ($isPullRequest) {
+ $baseNetwork = collect(["{$uuid}-{$pullRequestId}"]);
+ }
+
+ $parsedServices = collect([]);
+
+ $allMagicEnvironments = collect([]);
+ foreach ($services as $serviceName => $service) {
+ $magicEnvironments = collect([]);
+ $image = data_get_str($service, 'image');
+ $environment = collect(data_get($service, 'environment', []));
+ $buildArgs = collect(data_get($service, 'build.args', []));
+ $environment = $environment->merge($buildArgs);
+
+ $environment = collect(data_get($service, 'environment', []));
+ $buildArgs = collect(data_get($service, 'build.args', []));
+ $environment = $environment->merge($buildArgs);
+
+ // convert environment variables to one format
+ $environment = convertToKeyValueCollection($environment);
+
+ // Add Coolify defined environments
+ $allEnvironments = $resource->environment_variables()->get(['key', 'value']);
+
+ $allEnvironments = $allEnvironments->mapWithKeys(function ($item) {
+ return [$item['key'] => $item['value']];
+ });
+ // filter and add magic environments
+ foreach ($environment as $key => $value) {
+ // Get all SERVICE_ variables from keys and values
+ $key = str($key);
+ $value = str($value);
+ $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/';
+ preg_match_all($regex, $value, $valueMatches);
+ if (count($valueMatches[1]) > 0) {
+ foreach ($valueMatches[1] as $match) {
+ $match = replaceVariables($match);
+ if ($match->startsWith('SERVICE_')) {
+ if ($magicEnvironments->has($match->value())) {
+ continue;
+ }
+ $magicEnvironments->put($match->value(), '');
+ }
+ }
+ }
+ // Get magic environments where we need to preset the FQDN
+ // for example SERVICE_FQDN_APP_3000 (without a value)
+ if ($key->startsWith('SERVICE_FQDN_')) {
+ // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
+ if (substr_count(str($key)->value(), '_') === 3) {
+ $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value();
+ $port = $key->afterLast('_')->value();
+ } else {
+ $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
+ $port = null;
+ }
+ $fqdn = $resource->fqdn;
+ if (blank($resource->fqdn)) {
+ $fqdn = generateFqdn(server: $server, random: "$uuid", parserVersion: $resource->compose_parsing_version);
+ }
+
+ if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) {
+ $path = $value->value();
+ if ($path !== '/') {
+ $fqdn = "$fqdn$path";
+ }
+ }
+ $fqdnWithPort = $fqdn;
+ if ($port) {
+ $fqdnWithPort = "$fqdn:$port";
+ }
+ if (is_null($resource->fqdn)) {
+ data_forget($resource, 'environment_variables');
+ data_forget($resource, 'environment_variables_preview');
+ $resource->fqdn = $fqdnWithPort;
+ $resource->save();
+ }
+
+ if (substr_count(str($key)->value(), '_') === 2) {
+ $resource->environment_variables()->updateOrCreate([
+ 'key' => $key->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $fqdn,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ }
+ if (substr_count(str($key)->value(), '_') === 3) {
+
+ $newKey = str($key)->beforeLast('_');
+ $resource->environment_variables()->updateOrCreate([
+ 'key' => $newKey->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $fqdn,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ }
+ }
+ }
+
+ $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
+ if ($magicEnvironments->count() > 0) {
+ // Generate Coolify environment variables
+ foreach ($magicEnvironments as $key => $value) {
+ $key = str($key);
+ $value = replaceVariables($value);
+ $command = parseCommandFromMagicEnvVariable($key);
+ if ($command->value() === 'FQDN') {
+ $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
+ $originalFqdnFor = str($fqdnFor)->replace('_', '-');
+ if (str($fqdnFor)->contains('-')) {
+ $fqdnFor = str($fqdnFor)->replace('-', '_');
+ }
+ // Generated FQDN & URL
+ $fqdn = generateFqdn(server: $server, random: "$originalFqdnFor-$uuid", parserVersion: $resource->compose_parsing_version);
+ $url = generateUrl(server: $server, random: "$originalFqdnFor-$uuid");
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $fqdn,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ if ($resource->build_pack === 'dockercompose') {
+ $domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]);
+ $domainExists = data_get($domains->get($fqdnFor), 'domain');
+ $envExists = $resource->environment_variables()->where('key', $key->value())->first();
+ if (str($domainExists)->replace('http://', '')->replace('https://', '')->value() !== $envExists->value) {
+ $envExists->update([
+ 'value' => $url,
+ ]);
+ }
+ if (is_null($domainExists)) {
+ // Put URL in the domains array instead of FQDN
+ $domains->put((string) $fqdnFor, [
+ 'domain' => $url,
+ ]);
+ $resource->docker_compose_domains = $domains->toJson();
+ $resource->save();
+ }
+ }
+ } elseif ($command->value() === 'URL') {
+ $urlFor = $key->after('SERVICE_URL_')->lower()->value();
+ $originalUrlFor = str($urlFor)->replace('_', '-');
+ if (str($urlFor)->contains('-')) {
+ $urlFor = str($urlFor)->replace('-', '_');
+ }
+ $url = generateUrl(server: $server, random: "$originalUrlFor-$uuid");
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $url,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ if ($resource->build_pack === 'dockercompose') {
+ $domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]);
+ $domainExists = data_get($domains->get($urlFor), 'domain');
+ $envExists = $resource->environment_variables()->where('key', $key->value())->first();
+ if ($domainExists !== $envExists->value) {
+ $envExists->update([
+ 'value' => $url,
+ ]);
+ }
+ if (is_null($domainExists)) {
+ $domains->put((string) $urlFor, [
+ 'domain' => $url,
+ ]);
+ $resource->docker_compose_domains = $domains->toJson();
+ $resource->save();
+ }
+ }
+ } else {
+ $value = generateEnvValue($command, $resource);
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $value,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ }
+ }
+ }
+ }
+
+ // Parse the rest of the services
+ foreach ($services as $serviceName => $service) {
+ $image = data_get_str($service, 'image');
+ $restart = data_get_str($service, 'restart', RESTART_MODE);
+ $logging = data_get($service, 'logging');
+
+ if ($server->isLogDrainEnabled()) {
+ if ($resource->isLogDrainEnabled()) {
+ $logging = generate_fluentd_configuration();
+ }
+ }
+ $volumes = collect(data_get($service, 'volumes', []));
+ $networks = collect(data_get($service, 'networks', []));
+ $use_network_mode = data_get($service, 'network_mode') !== null;
+ $depends_on = collect(data_get($service, 'depends_on', []));
+ $labels = collect(data_get($service, 'labels', []));
+ if ($labels->count() > 0) {
+ if (isAssociativeArray($labels)) {
+ $newLabels = collect([]);
+ $labels->each(function ($value, $key) use ($newLabels) {
+ $newLabels->push("$key=$value");
+ });
+ $labels = $newLabels;
+ }
+ }
+ $environment = collect(data_get($service, 'environment', []));
+ $ports = collect(data_get($service, 'ports', []));
+ $buildArgs = collect(data_get($service, 'build.args', []));
+ $environment = $environment->merge($buildArgs);
+
+ $environment = convertToKeyValueCollection($environment);
+ $coolifyEnvironments = collect([]);
+
+ $isDatabase = isDatabaseImage($image, $service);
+ $volumesParsed = collect([]);
+
+ $baseName = generateApplicationContainerName(
+ application: $resource,
+ pull_request_id: $pullRequestId
+ );
+ $containerName = "$serviceName-$baseName";
+ $predefinedPort = null;
+
+ $originalResource = $resource;
+
+ if ($volumes->count() > 0) {
+ foreach ($volumes as $index => $volume) {
+ $type = null;
+ $source = null;
+ $target = null;
+ $content = null;
+ $isDirectory = false;
+ if (is_string($volume)) {
+ $source = str($volume)->beforeLast(':');
+ $target = str($volume)->afterLast(':');
+ $foundConfig = $fileStorages->whereMountPath($target)->first();
+ if (sourceIsLocal($source)) {
+ $type = str('bind');
+ if ($foundConfig) {
+ $contentNotNull_temp = data_get($foundConfig, 'content');
+ if ($contentNotNull_temp) {
+ $content = $contentNotNull_temp;
+ }
+ $isDirectory = data_get($foundConfig, 'is_directory');
+ } else {
+ // By default, we cannot determine if the bind is a directory or not, so we set it to directory
+ $isDirectory = true;
+ }
+ } else {
+ $type = str('volume');
+ }
+ } elseif (is_array($volume)) {
+ $type = data_get_str($volume, 'type');
+ $source = data_get_str($volume, 'source');
+ $target = data_get_str($volume, 'target');
+ $content = data_get($volume, 'content');
+ $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
+
+ $foundConfig = $fileStorages->whereMountPath($target)->first();
+ if ($foundConfig) {
+ $contentNotNull_temp = data_get($foundConfig, 'content');
+ if ($contentNotNull_temp) {
+ $content = $contentNotNull_temp;
+ }
+ $isDirectory = data_get($foundConfig, 'is_directory');
+ } else {
+ // if isDirectory is not set (or false) & content is also not set, we assume it is a directory
+ if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
+ $isDirectory = true;
+ }
+ }
+ }
+ if ($type->value() === 'bind') {
+ if ($source->value() === '/var/run/docker.sock') {
+ $volume = $source->value().':'.$target->value();
+ } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') {
+ $volume = $source->value().':'.$target->value();
+ } else {
+ if ((int) $resource->compose_parsing_version >= 4) {
+ $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
+ } else {
+ $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
+ }
+ $source = replaceLocalSource($source, $mainDirectory);
+ if ($isPullRequest) {
+ $source = $source."-pr-$pullRequestId";
+ }
+ LocalFileVolume::updateOrCreate(
+ [
+ 'mount_path' => $target,
+ 'resource_id' => $originalResource->id,
+ 'resource_type' => get_class($originalResource),
+ ],
+ [
+ 'fs_path' => $source,
+ 'mount_path' => $target,
+ 'content' => $content,
+ 'is_directory' => $isDirectory,
+ 'resource_id' => $originalResource->id,
+ 'resource_type' => get_class($originalResource),
+ ]
+ );
+ if (isDev()) {
+ if ((int) $resource->compose_parsing_version >= 4) {
+ $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
+ } else {
+ $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
+ }
+ }
+ $volume = "$source:$target";
+ }
+ } elseif ($type->value() === 'volume') {
+ if ($topLevel->get('volumes')->has($source->value())) {
+ $temp = $topLevel->get('volumes')->get($source->value());
+ if (data_get($temp, 'driver_opts.type') === 'cifs') {
+ continue;
+ }
+ if (data_get($temp, 'driver_opts.type') === 'nfs') {
+ continue;
+ }
+ }
+ $slugWithoutUuid = Str::slug($source, '-');
+ $name = "{$uuid}_{$slugWithoutUuid}";
+
+ if ($isPullRequest) {
+ $name = "{$name}-pr-$pullRequestId";
+ }
+ if (is_string($volume)) {
+ $source = str($volume)->beforeLast(':');
+ $target = str($volume)->afterLast(':');
+ $source = $name;
+ $volume = "$source:$target";
+ } elseif (is_array($volume)) {
+ data_set($volume, 'source', $name);
+ }
+ $topLevel->get('volumes')->put($name, [
+ 'name' => $name,
+ ]);
+ LocalPersistentVolume::updateOrCreate(
+ [
+ 'name' => $name,
+ 'resource_id' => $originalResource->id,
+ 'resource_type' => get_class($originalResource),
+ ],
+ [
+ 'name' => $name,
+ 'mount_path' => $target,
+ 'resource_id' => $originalResource->id,
+ 'resource_type' => get_class($originalResource),
+ ]
+ );
+ }
+ dispatch(new ServerFilesFromServerJob($originalResource));
+ $volumesParsed->put($index, $volume);
+ }
+ }
+
+ if ($depends_on?->count() > 0) {
+ if ($isPullRequest) {
+ $newDependsOn = collect([]);
+ $depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) {
+ if (is_numeric($condition)) {
+ $dependency = "$dependency-pr-$pullRequestId";
+
+ $newDependsOn->put($condition, $dependency);
+ } else {
+ $condition = "$condition-pr-$pullRequestId";
+ $newDependsOn->put($condition, $dependency);
+ }
+ });
+ $depends_on = $newDependsOn;
+ }
+ }
+ if (! $use_network_mode) {
+ if ($topLevel->get('networks')?->count() > 0) {
+ foreach ($topLevel->get('networks') as $networkName => $network) {
+ if ($networkName === 'default') {
+ continue;
+ }
+ // ignore aliases
+ if ($network['aliases'] ?? false) {
+ continue;
+ }
+ $networkExists = $networks->contains(function ($value, $key) use ($networkName) {
+ return $value == $networkName || $key == $networkName;
+ });
+ if (! $networkExists) {
+ $networks->put($networkName, null);
+ }
+ }
+ }
+ $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) {
+ return $value == $baseNetwork;
+ });
+ if (! $baseNetworkExists) {
+ foreach ($baseNetwork as $network) {
+ $topLevel->get('networks')->put($network, [
+ 'name' => $network,
+ 'external' => true,
+ ]);
+ }
+ }
+ }
+
+ // Collect/create/update ports
+ $collectedPorts = collect([]);
+ if ($ports->count() > 0) {
+ foreach ($ports as $sport) {
+ if (is_string($sport) || is_numeric($sport)) {
+ $collectedPorts->push($sport);
+ }
+ if (is_array($sport)) {
+ $target = data_get($sport, 'target');
+ $published = data_get($sport, 'published');
+ $protocol = data_get($sport, 'protocol');
+ $collectedPorts->push("$target:$published/$protocol");
+ }
+ }
+ }
+
+ $networks_temp = collect();
+
+ if (! $use_network_mode) {
+ foreach ($networks as $key => $network) {
+ if (gettype($network) === 'string') {
+ // networks:
+ // - appwrite
+ $networks_temp->put($network, null);
+ } elseif (gettype($network) === 'array') {
+ // networks:
+ // default:
+ // ipv4_address: 192.168.203.254
+ $networks_temp->put($key, $network);
+ }
+ }
+ foreach ($baseNetwork as $key => $network) {
+ $networks_temp->put($network, null);
+ }
+
+ if (data_get($resource, 'settings.connect_to_docker_network')) {
+ $network = $resource->destination->network;
+ $networks_temp->put($network, null);
+ $topLevel->get('networks')->put($network, [
+ 'name' => $network,
+ 'external' => true,
+ ]);
+ }
+ }
+
+ $normalEnvironments = $environment->diffKeys($allMagicEnvironments);
+ $normalEnvironments = $normalEnvironments->filter(function ($value, $key) {
+ return ! str($value)->startsWith('SERVICE_');
+ });
+ foreach ($normalEnvironments as $key => $value) {
+ $key = str($key);
+ $value = str($value);
+ $originalValue = $value;
+ $parsedValue = replaceVariables($value);
+ if ($value->startsWith('$SERVICE_')) {
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key,
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $value,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+
+ continue;
+ }
+ if (! $value->startsWith('$')) {
+ continue;
+ }
+ if ($key->value() === $parsedValue->value()) {
+ $value = null;
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key,
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $value,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ } else {
+ if ($value->startsWith('$')) {
+ $isRequired = false;
+ if ($value->contains(':-')) {
+ $value = replaceVariables($value);
+ $key = $value->before(':');
+ $value = $value->after(':-');
+ } elseif ($value->contains('-')) {
+ $value = replaceVariables($value);
+
+ $key = $value->before('-');
+ $value = $value->after('-');
+ } elseif ($value->contains(':?')) {
+ $value = replaceVariables($value);
+
+ $key = $value->before(':');
+ $value = $value->after(':?');
+ $isRequired = true;
+ } elseif ($value->contains('?')) {
+ $value = replaceVariables($value);
+
+ $key = $value->before('?');
+ $value = $value->after('?');
+ $isRequired = true;
+ }
+ if ($originalValue->value() === $value->value()) {
+ // This means the variable does not have a default value, so it needs to be created in Coolify
+ $parsedKeyValue = replaceVariables($value);
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $parsedKeyValue,
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ 'is_required' => $isRequired,
+ ]);
+ // Add the variable to the environment so it will be shown in the deployable compose file
+ $environment[$parsedKeyValue->value()] = $value;
+
+ continue;
+ }
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key,
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $value,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ 'is_required' => $isRequired,
+ ]);
+ }
+ }
+ }
+ $branch = $originalResource->git_branch;
+ if ($pullRequestId !== 0) {
+ $branch = "pull/{$pullRequestId}/head";
+ }
+ if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
+ $coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\"");
+ }
+
+ // Add COOLIFY_RESOURCE_UUID to environment
+ if ($resource->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) {
+ $coolifyEnvironments->put('COOLIFY_RESOURCE_UUID', "{$resource->uuid}");
+ }
+
+ // Add COOLIFY_CONTAINER_NAME to environment
+ if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
+ $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "{$containerName}");
+ }
+
+ if ($isPullRequest) {
+ $preview = $resource->previews()->find($preview_id);
+ $domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))) ?? collect([]);
+ } else {
+ $domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]);
+ }
+
+ // Only process domains for dockercompose applications to prevent SERVICE variable recreation
+ if ($resource->build_pack !== 'dockercompose') {
+ $domains = collect([]);
+ }
+ $fqdns = data_get($domains, "$serviceName.domain");
+ // Generate SERVICE_FQDN & SERVICE_URL for dockercompose
+ if ($resource->build_pack === 'dockercompose') {
+ foreach ($domains as $forServiceName => $domain) {
+ $parsedDomain = data_get($domain, 'domain');
+ $serviceNameFormatted = str($serviceName)->upper()->replace('-', '_');
+
+ if (filled($parsedDomain)) {
+ $parsedDomain = str($parsedDomain)->explode(',')->first();
+ $coolifyUrl = Url::fromString($parsedDomain);
+ $coolifyScheme = $coolifyUrl->getScheme();
+ $coolifyFqdn = $coolifyUrl->getHost();
+ $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
+ $coolifyEnvironments->put('SERVICE_URL_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyUrl->__toString());
+ $coolifyEnvironments->put('SERVICE_FQDN_'.str($forServiceName)->upper()->replace('-', '_'), $coolifyFqdn);
+ $resource->environment_variables()->updateOrCreate([
+ 'resourceable_type' => Application::class,
+ 'resourceable_id' => $resource->id,
+ 'key' => 'SERVICE_URL_'.str($forServiceName)->upper()->replace('-', '_'),
+ ], [
+ 'value' => $coolifyUrl->__toString(),
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ $resource->environment_variables()->updateOrCreate([
+ 'resourceable_type' => Application::class,
+ 'resourceable_id' => $resource->id,
+ 'key' => 'SERVICE_FQDN_'.str($forServiceName)->upper()->replace('-', '_'),
+ ], [
+ 'value' => $coolifyFqdn,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ } else {
+ $resource->environment_variables()->where('resourceable_type', Application::class)
+ ->where('resourceable_id', $resource->id)
+ ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceNameFormatted}%")
+ ->update([
+ 'value' => null,
+ ]);
+ $resource->environment_variables()->where('resourceable_type', Application::class)
+ ->where('resourceable_id', $resource->id)
+ ->where('key', 'LIKE', "SERVICE_URL_{$serviceNameFormatted}%")
+ ->update([
+ 'value' => null,
+ ]);
+ }
+ }
+ }
+ // If the domain is set, we need to generate the FQDNs for the preview
+ if (filled($fqdns)) {
+ $fqdns = str($fqdns)->explode(',');
+ if ($isPullRequest) {
+ $preview = $resource->previews()->find($preview_id);
+ $docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains')));
+ if ($docker_compose_domains->count() > 0) {
+ $found_fqdn = data_get($docker_compose_domains, "$serviceName.domain");
+ if ($found_fqdn) {
+ $fqdns = collect($found_fqdn);
+ } else {
+ $fqdns = collect([]);
+ }
+ } else {
+ $fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId, $resource) {
+ $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pullRequestId);
+ $url = Url::fromString($fqdn);
+ $template = $resource->preview_url_template;
+ $host = $url->getHost();
+ $schema = $url->getScheme();
+ $random = new Cuid2;
+ $preview_fqdn = str_replace('{{random}}', $random, $template);
+ $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
+ $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn);
+ $preview_fqdn = "$schema://$preview_fqdn";
+ $preview->fqdn = $preview_fqdn;
+ $preview->save();
+
+ return $preview_fqdn;
+ });
+ }
+ }
+ }
+ $defaultLabels = defaultLabels(
+ id: $resource->id,
+ name: $containerName,
+ projectName: $resource->project()->name,
+ resourceName: $resource->name,
+ pull_request_id: $pullRequestId,
+ type: 'application',
+ environment: $resource->environment->name,
+ );
+
+ $isDatabase = isDatabaseImage($image, $service);
+ // Add COOLIFY_FQDN & COOLIFY_URL to environment
+ if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
+ $fqdnsWithoutPort = $fqdns->map(function ($fqdn) {
+ return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://'));
+ });
+ $coolifyEnvironments->put('COOLIFY_URL', $fqdnsWithoutPort->implode(','));
+
+ $urls = $fqdns->map(function ($fqdn) {
+ return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':');
+ });
+ $coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(','));
+ }
+ add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables);
+ if ($environment->count() > 0) {
+ $environment = $environment->filter(function ($value, $key) {
+ return ! str($key)->startsWith('SERVICE_FQDN_');
+ })->map(function ($value, $key) use ($resource) {
+ // if value is empty, set it to null so if you set the environment variable in the .env file (Coolify's UI), it will used
+ if (str($value)->isEmpty()) {
+ if ($resource->environment_variables()->where('key', $key)->exists()) {
+ $value = $resource->environment_variables()->where('key', $key)->first()->value;
+ } else {
+ $value = null;
+ }
+ }
+
+ return $value;
+ });
+ }
+ $serviceLabels = $labels->merge($defaultLabels);
+ if ($serviceLabels->count() > 0) {
+ $isContainerLabelEscapeEnabled = data_get($resource, 'settings.is_container_label_escape_enabled');
+ if ($isContainerLabelEscapeEnabled) {
+ $serviceLabels = $serviceLabels->map(function ($value, $key) {
+ return escapeDollarSign($value);
+ });
+ }
+ }
+ if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
+ $shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels;
+ $uuid = $resource->uuid;
+ $network = data_get($resource, 'destination.network');
+ if ($isPullRequest) {
+ $uuid = "{$resource->uuid}-{$pullRequestId}";
+ }
+ if ($isPullRequest) {
+ $network = "{$resource->destination->network}-{$pullRequestId}";
+ }
+ if ($shouldGenerateLabelsExactly) {
+ switch ($server->proxyType()) {
+ case ProxyTypes::TRAEFIK->value:
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
+ uuid: $uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $originalResource->isGzipEnabled(),
+ is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: $image
+ ));
+ break;
+ case ProxyTypes::CADDY->value:
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
+ network: $network,
+ uuid: $uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $originalResource->isGzipEnabled(),
+ is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: $image,
+ predefinedPort: $predefinedPort
+ ));
+ break;
+ }
+ } else {
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
+ uuid: $uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $originalResource->isGzipEnabled(),
+ is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: $image
+ ));
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
+ network: $network,
+ uuid: $uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $originalResource->isGzipEnabled(),
+ is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: $image,
+ predefinedPort: $predefinedPort
+ ));
+ }
+ }
+ data_forget($service, 'volumes.*.content');
+ data_forget($service, 'volumes.*.isDirectory');
+ data_forget($service, 'volumes.*.is_directory');
+ data_forget($service, 'exclude_from_hc');
+
+ $volumesParsed = $volumesParsed->map(function ($volume) {
+ data_forget($volume, 'content');
+ data_forget($volume, 'is_directory');
+ data_forget($volume, 'isDirectory');
+
+ return $volume;
+ });
+
+ $payload = collect($service)->merge([
+ 'container_name' => $containerName,
+ 'restart' => $restart->value(),
+ 'labels' => $serviceLabels,
+ ]);
+ if (! $use_network_mode) {
+ $payload['networks'] = $networks_temp;
+ }
+ if ($ports->count() > 0) {
+ $payload['ports'] = $ports;
+ }
+ if ($volumesParsed->count() > 0) {
+ $payload['volumes'] = $volumesParsed;
+ }
+ if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) {
+ $payload['environment'] = $environment->merge($coolifyEnvironments);
+ }
+ if ($logging) {
+ $payload['logging'] = $logging;
+ }
+ if ($depends_on->count() > 0) {
+ $payload['depends_on'] = $depends_on;
+ }
+ if ($isPullRequest) {
+ $serviceName = "{$serviceName}-pr-{$pullRequestId}";
+ }
+
+ $parsedServices->put($serviceName, $payload);
+ }
+ $topLevel->put('services', $parsedServices);
+
+ $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
+
+ $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
+ return array_search($key, $customOrder);
+ });
+
+ $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2);
+ data_forget($resource, 'environment_variables');
+ data_forget($resource, 'environment_variables_preview');
+ $resource->save();
+
+ return $topLevel;
+}
+
+function serviceParser(Service $resource): Collection
+{
+ $uuid = data_get($resource, 'uuid');
+ $compose = data_get($resource, 'docker_compose_raw');
+ if (! $compose) {
+ return collect([]);
+ }
+
+ $server = data_get($resource, 'server');
+ $allServices = get_service_templates();
+
+ try {
+ $yaml = Yaml::parse($compose);
+ } catch (\Exception) {
+ return collect([]);
+ }
+ $services = data_get($yaml, 'services', collect([]));
+ $topLevel = collect([
+ 'volumes' => collect(data_get($yaml, 'volumes', [])),
+ 'networks' => collect(data_get($yaml, 'networks', [])),
+ 'configs' => collect(data_get($yaml, 'configs', [])),
+ 'secrets' => collect(data_get($yaml, 'secrets', [])),
+ ]);
+ // If there are predefined volumes, make sure they are not null
+ if ($topLevel->get('volumes')->count() > 0) {
+ $temp = collect([]);
+ foreach ($topLevel['volumes'] as $volumeName => $volume) {
+ if (is_null($volume)) {
+ continue;
+ }
+ $temp->put($volumeName, $volume);
+ }
+ $topLevel['volumes'] = $temp;
+ }
+ // Get the base docker network
+ $baseNetwork = collect([$uuid]);
+
+ $parsedServices = collect([]);
+
+ $allMagicEnvironments = collect([]);
+ // Presave services
+ foreach ($services as $serviceName => $service) {
+ $image = data_get_str($service, 'image');
+ $isDatabase = isDatabaseImage($image, $service);
+ if ($isDatabase) {
+ $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
+ if ($applicationFound) {
+ $savedService = $applicationFound;
+ } else {
+ $savedService = ServiceDatabase::firstOrCreate([
+ 'name' => $serviceName,
+ 'image' => $image,
+ 'service_id' => $resource->id,
+ ]);
+ }
+ } else {
+ $savedService = ServiceApplication::firstOrCreate([
+ 'name' => $serviceName,
+ 'image' => $image,
+ 'service_id' => $resource->id,
+ ]);
+ }
+ }
+ foreach ($services as $serviceName => $service) {
+ $predefinedPort = null;
+ $magicEnvironments = collect([]);
+ $image = data_get_str($service, 'image');
+ $environment = collect(data_get($service, 'environment', []));
+ $buildArgs = collect(data_get($service, 'build.args', []));
+ $environment = $environment->merge($buildArgs);
+ $isDatabase = isDatabaseImage($image, $service);
+
+ $containerName = "$serviceName-{$resource->uuid}";
+
+ if ($serviceName === 'registry') {
+ $tempServiceName = 'docker-registry';
+ } else {
+ $tempServiceName = $serviceName;
+ }
+ if (str(data_get($service, 'image'))->contains('glitchtip')) {
+ $tempServiceName = 'glitchtip';
+ }
+ if ($serviceName === 'supabase-kong') {
+ $tempServiceName = 'supabase';
+ }
+ $serviceDefinition = data_get($allServices, $tempServiceName);
+ $predefinedPort = data_get($serviceDefinition, 'port');
+ if ($serviceName === 'plausible') {
+ $predefinedPort = '8000';
+ }
+ if ($isDatabase) {
+ $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first();
+ if ($applicationFound) {
+ $savedService = $applicationFound;
+ } else {
+ $savedService = ServiceDatabase::firstOrCreate([
+ 'name' => $serviceName,
+ 'service_id' => $resource->id,
+ ]);
+ }
+ } else {
+ $savedService = ServiceApplication::firstOrCreate([
+ 'name' => $serviceName,
+ 'service_id' => $resource->id,
+ ], [
+ 'is_gzip_enabled' => true,
+ ]);
+ }
+ // Check if image changed
+ if ($savedService->image !== $image) {
+ $savedService->image = $image;
+ $savedService->save();
+ }
+ // Pocketbase does not need gzip for SSE.
+ if (str($savedService->image)->contains('pocketbase') && $savedService->is_gzip_enabled) {
+ $savedService->is_gzip_enabled = false;
+ $savedService->save();
+ }
+
+ $environment = collect(data_get($service, 'environment', []));
+ $buildArgs = collect(data_get($service, 'build.args', []));
+ $environment = $environment->merge($buildArgs);
+
+ // convert environment variables to one format
+ $environment = convertToKeyValueCollection($environment);
+
+ // Add Coolify defined environments
+ $allEnvironments = $resource->environment_variables()->get(['key', 'value']);
+
+ $allEnvironments = $allEnvironments->mapWithKeys(function ($item) {
+ return [$item['key'] => $item['value']];
+ });
+ // filter and add magic environments
+ foreach ($environment as $key => $value) {
+ // Get all SERVICE_ variables from keys and values
+ $key = str($key);
+ $value = str($value);
+ $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/';
+ preg_match_all($regex, $value, $valueMatches);
+ if (count($valueMatches[1]) > 0) {
+ foreach ($valueMatches[1] as $match) {
+ $match = replaceVariables($match);
+ if ($match->startsWith('SERVICE_')) {
+ if ($magicEnvironments->has($match->value())) {
+ continue;
+ }
+ $magicEnvironments->put($match->value(), '');
+ }
+ }
+ }
+ // Get magic environments where we need to preset the FQDN / URL
+ if ($key->startsWith('SERVICE_FQDN_') || $key->startsWith('SERVICE_URL_')) {
+ // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
+ if (substr_count(str($key)->value(), '_') === 3) {
+ if ($key->startsWith('SERVICE_FQDN_')) {
+ $urlFor = null;
+ $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value();
+ }
+ if ($key->startsWith('SERVICE_URL_')) {
+ $fqdnFor = null;
+ $urlFor = $key->after('SERVICE_URL_')->beforeLast('_')->lower()->value();
+ }
+ $port = $key->afterLast('_')->value();
+ } else {
+ if ($key->startsWith('SERVICE_FQDN_')) {
+ $urlFor = null;
+ $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
+ }
+ if ($key->startsWith('SERVICE_URL_')) {
+ $fqdnFor = null;
+ $urlFor = $key->after('SERVICE_URL_')->lower()->value();
+ }
+ $port = null;
+ }
+ if (blank($savedService->fqdn)) {
+ if ($fqdnFor) {
+ $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version);
+ } else {
+ $fqdn = generateFqdn(server: $server, random: "{$savedService->name}-$uuid", parserVersion: $resource->compose_parsing_version);
+ }
+ if ($urlFor) {
+ $url = generateUrl($server, "$urlFor-$uuid");
+ } else {
+ $url = generateUrl($server, "{$savedService->name}-$uuid");
+ }
+ } else {
+ $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value();
+ $url = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value();
+ }
+
+ if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) {
+ $path = $value->value();
+ if ($path !== '/') {
+ $fqdn = "$fqdn$path";
+ $url = "$url$path";
+ }
+ }
+ $fqdnWithPort = $fqdn;
+ $urlWithPort = $url;
+ if ($fqdn && $port) {
+ $fqdnWithPort = "$fqdn:$port";
+ }
+ if ($url && $port) {
+ $urlWithPort = "$url:$port";
+ }
+ if (is_null($savedService->fqdn)) {
+ if ((int) $resource->compose_parsing_version >= 5 && version_compare(config('constants.coolify.version'), '4.0.0-beta.420.7', '>=')) {
+ if ($fqdnFor) {
+ $savedService->fqdn = $fqdnWithPort;
+ }
+ if ($urlFor) {
+ $savedService->fqdn = $urlWithPort;
+ }
+ } else {
+ $savedService->fqdn = $fqdnWithPort;
+ }
+ $savedService->save();
+ }
+ if (substr_count(str($key)->value(), '_') === 2) {
+ $resource->environment_variables()->updateOrCreate([
+ 'key' => $key->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $fqdn,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ $resource->environment_variables()->updateOrCreate([
+ 'key' => $key->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $url,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ }
+ if (substr_count(str($key)->value(), '_') === 3) {
+ $newKey = str($key)->beforeLast('_');
+ $resource->environment_variables()->updateOrCreate([
+ 'key' => $newKey->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $fqdn,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ $resource->environment_variables()->updateOrCreate([
+ 'key' => $newKey->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $url,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ }
+ }
+ }
+ $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
+ if ($magicEnvironments->count() > 0) {
+ foreach ($magicEnvironments as $key => $value) {
+ $key = str($key);
+ $value = replaceVariables($value);
+ $command = parseCommandFromMagicEnvVariable($key);
+ if ($command->value() === 'FQDN') {
+ $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
+ $fqdn = generateFqdn(server: $server, random: str($fqdnFor)->replace('_', '-')->value()."-$uuid", parserVersion: $resource->compose_parsing_version);
+ $url = generateUrl(server: $server, random: str($fqdnFor)->replace('_', '-')->value()."-$uuid");
+
+ $envExists = $resource->environment_variables()->where('key', $key->value())->first();
+ $serviceExists = ServiceApplication::where('name', str($fqdnFor)->replace('_', '-')->value())->where('service_id', $resource->id)->first();
+ if (! $envExists && (data_get($serviceExists, 'name') === str($fqdnFor)->replace('_', '-')->value())) {
+ // Save URL otherwise it won't work.
+ $serviceExists->fqdn = $url;
+ $serviceExists->save();
+ }
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $fqdn,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+
+ } elseif ($command->value() === 'URL') {
+ $urlFor = $key->after('SERVICE_URL_')->lower()->value();
+ $url = generateUrl(server: $server, random: str($urlFor)->replace('_', '-')->value()."-$uuid");
+
+ $envExists = $resource->environment_variables()->where('key', $key->value())->first();
+ $serviceExists = ServiceApplication::where('name', str($urlFor)->replace('_', '-')->value())->where('service_id', $resource->id)->first();
+ if (! $envExists && (data_get($serviceExists, 'name') === str($urlFor)->replace('_', '-')->value())) {
+ $serviceExists->fqdn = $url;
+ $serviceExists->save();
+ }
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $url,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+
+ } else {
+ $value = generateEnvValue($command, $resource);
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key->value(),
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $value,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ }
+ }
+ }
+ }
+
+ $serviceAppsLogDrainEnabledMap = $resource->applications()->get()->keyBy('name')->map(function ($app) {
+ return $app->isLogDrainEnabled();
+ });
+
+ // Parse the rest of the services
+ foreach ($services as $serviceName => $service) {
+ $image = data_get_str($service, 'image');
+ $restart = data_get_str($service, 'restart', RESTART_MODE);
+ $logging = data_get($service, 'logging');
+
+ if ($server->isLogDrainEnabled()) {
+ if ($serviceAppsLogDrainEnabledMap->get($serviceName)) {
+ $logging = generate_fluentd_configuration();
+ }
+ }
+ $volumes = collect(data_get($service, 'volumes', []));
+ $networks = collect(data_get($service, 'networks', []));
+ $use_network_mode = data_get($service, 'network_mode') !== null;
+ $depends_on = collect(data_get($service, 'depends_on', []));
+ $labels = collect(data_get($service, 'labels', []));
+ if ($labels->count() > 0) {
+ if (isAssociativeArray($labels)) {
+ $newLabels = collect([]);
+ $labels->each(function ($value, $key) use ($newLabels) {
+ $newLabels->push("$key=$value");
+ });
+ $labels = $newLabels;
+ }
+ }
+ $environment = collect(data_get($service, 'environment', []));
+ $ports = collect(data_get($service, 'ports', []));
+ $buildArgs = collect(data_get($service, 'build.args', []));
+ $environment = $environment->merge($buildArgs);
+
+ $environment = convertToKeyValueCollection($environment);
+ $coolifyEnvironments = collect([]);
+
+ $isDatabase = isDatabaseImage($image, $service);
+ $volumesParsed = collect([]);
+
+ $containerName = "$serviceName-{$resource->uuid}";
+
+ if ($serviceName === 'registry') {
+ $tempServiceName = 'docker-registry';
+ } else {
+ $tempServiceName = $serviceName;
+ }
+ if (str(data_get($service, 'image'))->contains('glitchtip')) {
+ $tempServiceName = 'glitchtip';
+ }
+ if ($serviceName === 'supabase-kong') {
+ $tempServiceName = 'supabase';
+ }
+ $serviceDefinition = data_get($allServices, $tempServiceName);
+ $predefinedPort = data_get($serviceDefinition, 'port');
+ if ($serviceName === 'plausible') {
+ $predefinedPort = '8000';
+ }
+
+ if ($isDatabase) {
+ $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
+ if ($applicationFound) {
+ $savedService = $applicationFound;
+ } else {
+ $savedService = ServiceDatabase::firstOrCreate([
+ 'name' => $serviceName,
+ 'image' => $image,
+ 'service_id' => $resource->id,
+ ]);
+ }
+ } else {
+ $savedService = ServiceApplication::firstOrCreate([
+ 'name' => $serviceName,
+ 'image' => $image,
+ 'service_id' => $resource->id,
+ ]);
+ }
+ $fileStorages = $savedService->fileStorages();
+ if ($savedService->image !== $image) {
+ $savedService->image = $image;
+ $savedService->save();
+ }
+
+ $originalResource = $savedService;
+
+ if ($volumes->count() > 0) {
+ foreach ($volumes as $index => $volume) {
+ $type = null;
+ $source = null;
+ $target = null;
+ $content = null;
+ $isDirectory = false;
+ if (is_string($volume)) {
+ $source = str($volume)->beforeLast(':');
+ $target = str($volume)->afterLast(':');
+ $foundConfig = $fileStorages->whereMountPath($target)->first();
+ if (sourceIsLocal($source)) {
+ $type = str('bind');
+ if ($foundConfig) {
+ $contentNotNull_temp = data_get($foundConfig, 'content');
+ if ($contentNotNull_temp) {
+ $content = $contentNotNull_temp;
+ }
+ $isDirectory = data_get($foundConfig, 'is_directory');
+ } else {
+ // By default, we cannot determine if the bind is a directory or not, so we set it to directory
+ $isDirectory = true;
+ }
+ } else {
+ $type = str('volume');
+ }
+ } elseif (is_array($volume)) {
+ $type = data_get_str($volume, 'type');
+ $source = data_get_str($volume, 'source');
+ $target = data_get_str($volume, 'target');
+ $content = data_get($volume, 'content');
+ $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
+
+ $foundConfig = $fileStorages->whereMountPath($target)->first();
+ if ($foundConfig) {
+ $contentNotNull_temp = data_get($foundConfig, 'content');
+ if ($contentNotNull_temp) {
+ $content = $contentNotNull_temp;
+ }
+ $isDirectory = data_get($foundConfig, 'is_directory');
+ } else {
+ // if isDirectory is not set (or false) & content is also not set, we assume it is a directory
+ if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
+ $isDirectory = true;
+ }
+ }
+ }
+ if ($type->value() === 'bind') {
+ if ($source->value() === '/var/run/docker.sock') {
+ $volume = $source->value().':'.$target->value();
+ } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') {
+ $volume = $source->value().':'.$target->value();
+ } else {
+ if ((int) $resource->compose_parsing_version >= 4) {
+ $mainDirectory = str(base_configuration_dir().'/services/'.$uuid);
+ } else {
+ $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
+ }
+ $source = replaceLocalSource($source, $mainDirectory);
+ LocalFileVolume::updateOrCreate(
+ [
+ 'mount_path' => $target,
+ 'resource_id' => $originalResource->id,
+ 'resource_type' => get_class($originalResource),
+ ],
+ [
+ 'fs_path' => $source,
+ 'mount_path' => $target,
+ 'content' => $content,
+ 'is_directory' => $isDirectory,
+ 'resource_id' => $originalResource->id,
+ 'resource_type' => get_class($originalResource),
+ ]
+ );
+ if (isDev()) {
+ if ((int) $resource->compose_parsing_version >= 4) {
+ $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid);
+ } else {
+ $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
+ }
+ }
+ $volume = "$source:$target";
+ }
+ } elseif ($type->value() === 'volume') {
+ if ($topLevel->get('volumes')->has($source->value())) {
+ $temp = $topLevel->get('volumes')->get($source->value());
+ if (data_get($temp, 'driver_opts.type') === 'cifs') {
+ continue;
+ }
+ if (data_get($temp, 'driver_opts.type') === 'nfs') {
+ continue;
+ }
+ }
+ $slugWithoutUuid = Str::slug($source, '-');
+ $name = "{$uuid}_{$slugWithoutUuid}";
+
+ if (is_string($volume)) {
+ $source = str($volume)->beforeLast(':');
+ $target = str($volume)->afterLast(':');
+ $source = $name;
+ $volume = "$source:$target";
+ } elseif (is_array($volume)) {
+ data_set($volume, 'source', $name);
+ }
+ $topLevel->get('volumes')->put($name, [
+ 'name' => $name,
+ ]);
+ LocalPersistentVolume::updateOrCreate(
+ [
+ 'name' => $name,
+ 'resource_id' => $originalResource->id,
+ 'resource_type' => get_class($originalResource),
+ ],
+ [
+ 'name' => $name,
+ 'mount_path' => $target,
+ 'resource_id' => $originalResource->id,
+ 'resource_type' => get_class($originalResource),
+ ]
+ );
+ }
+ dispatch(new ServerFilesFromServerJob($originalResource));
+ $volumesParsed->put($index, $volume);
+ }
+ }
+
+ if (! $use_network_mode) {
+ if ($topLevel->get('networks')?->count() > 0) {
+ foreach ($topLevel->get('networks') as $networkName => $network) {
+ if ($networkName === 'default') {
+ continue;
+ }
+ // ignore aliases
+ if ($network['aliases'] ?? false) {
+ continue;
+ }
+ $networkExists = $networks->contains(function ($value, $key) use ($networkName) {
+ return $value == $networkName || $key == $networkName;
+ });
+ if (! $networkExists) {
+ $networks->put($networkName, null);
+ }
+ }
+ }
+ $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) {
+ return $value == $baseNetwork;
+ });
+ if (! $baseNetworkExists) {
+ foreach ($baseNetwork as $network) {
+ $topLevel->get('networks')->put($network, [
+ 'name' => $network,
+ 'external' => true,
+ ]);
+ }
+ }
+ }
+
+ // Collect/create/update ports
+ $collectedPorts = collect([]);
+ if ($ports->count() > 0) {
+ foreach ($ports as $sport) {
+ if (is_string($sport) || is_numeric($sport)) {
+ $collectedPorts->push($sport);
+ }
+ if (is_array($sport)) {
+ $target = data_get($sport, 'target');
+ $published = data_get($sport, 'published');
+ $protocol = data_get($sport, 'protocol');
+ $collectedPorts->push("$target:$published/$protocol");
+ }
+ }
+ }
+ $originalResource->ports = $collectedPorts->implode(',');
+ $originalResource->save();
+
+ $networks_temp = collect();
+
+ if (! $use_network_mode) {
+ foreach ($networks as $key => $network) {
+ if (gettype($network) === 'string') {
+ // networks:
+ // - appwrite
+ $networks_temp->put($network, null);
+ } elseif (gettype($network) === 'array') {
+ // networks:
+ // default:
+ // ipv4_address: 192.168.203.254
+ $networks_temp->put($key, $network);
+ }
+ }
+ foreach ($baseNetwork as $key => $network) {
+ $networks_temp->put($network, null);
+ }
+ }
+
+ $normalEnvironments = $environment->diffKeys($allMagicEnvironments);
+ $normalEnvironments = $normalEnvironments->filter(function ($value, $key) {
+ return ! str($value)->startsWith('SERVICE_');
+ });
+ foreach ($normalEnvironments as $key => $value) {
+ $key = str($key);
+ $value = str($value);
+ $originalValue = $value;
+ $parsedValue = replaceVariables($value);
+ if ($parsedValue->startsWith('SERVICE_')) {
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key,
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $value,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+
+ continue;
+ }
+ if (! $value->startsWith('$')) {
+ continue;
+ }
+ if ($key->value() === $parsedValue->value()) {
+ $value = null;
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key,
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $value,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ } else {
+ if ($value->startsWith('$')) {
+ $isRequired = false;
+ if ($value->contains(':-')) {
+ $value = replaceVariables($value);
+ $key = $value->before(':');
+ $value = $value->after(':-');
+ } elseif ($value->contains('-')) {
+ $value = replaceVariables($value);
+
+ $key = $value->before('-');
+ $value = $value->after('-');
+ } elseif ($value->contains(':?')) {
+ $value = replaceVariables($value);
+
+ $key = $value->before(':');
+ $value = $value->after(':?');
+ $isRequired = true;
+ } elseif ($value->contains('?')) {
+ $value = replaceVariables($value);
+
+ $key = $value->before('?');
+ $value = $value->after('?');
+ $isRequired = true;
+ }
+ if ($originalValue->value() === $value->value()) {
+ // This means the variable does not have a default value, so it needs to be created in Coolify
+ $parsedKeyValue = replaceVariables($value);
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $parsedKeyValue,
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ 'is_required' => $isRequired,
+ ]);
+ // Add the variable to the environment so it will be shown in the deployable compose file
+ $environment[$parsedKeyValue->value()] = $value;
+
+ continue;
+ }
+ $resource->environment_variables()->firstOrCreate([
+ 'key' => $key,
+ 'resourceable_type' => get_class($resource),
+ 'resourceable_id' => $resource->id,
+ ], [
+ 'value' => $value,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ 'is_required' => $isRequired,
+ ]);
+ }
+ }
+ }
+
+ // Add COOLIFY_RESOURCE_UUID to environment
+ if ($resource->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) {
+ $coolifyEnvironments->put('COOLIFY_RESOURCE_UUID', "{$resource->uuid}");
+ }
+
+ // Add COOLIFY_CONTAINER_NAME to environment
+ if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
+ $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "{$containerName}");
+ }
+
+ if ($savedService->serviceType()) {
+ $fqdns = generateServiceSpecificFqdns($savedService);
+ } else {
+ $fqdns = collect(data_get($savedService, 'fqdns'))->filter();
+ }
+
+ $defaultLabels = defaultLabels(
+ id: $resource->id,
+ name: $containerName,
+ projectName: $resource->project()->name,
+ resourceName: $resource->name,
+ type: 'service',
+ subType: $isDatabase ? 'database' : 'application',
+ subId: $savedService->id,
+ subName: $savedService->human_name ?? $savedService->name,
+ environment: $resource->environment->name,
+ );
+
+ // Add COOLIFY_FQDN & COOLIFY_URL to environment
+ if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
+ $fqdnsWithoutPort = $fqdns->map(function ($fqdn) {
+ return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':');
+ });
+ $coolifyEnvironments->put('COOLIFY_FQDN', $fqdnsWithoutPort->implode(','));
+ $urls = $fqdns->map(function ($fqdn): Stringable {
+ return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://'));
+ });
+ $coolifyEnvironments->put('COOLIFY_URL', $urls->implode(','));
+ }
+ add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables);
+ if ($environment->count() > 0) {
+ $environment = $environment->filter(function ($value, $key) {
+ return ! str($key)->startsWith('SERVICE_FQDN_');
+ })->map(function ($value, $key) use ($resource) {
+ // if value is empty, set it to null so if you set the environment variable in the .env file (Coolify's UI), it will used
+ if (str($value)->isEmpty()) {
+ if ($resource->environment_variables()->where('key', $key)->exists()) {
+ $value = $resource->environment_variables()->where('key', $key)->first()->value;
+ } else {
+ $value = null;
+ }
+ }
+
+ return $value;
+ });
+ }
+ $serviceLabels = $labels->merge($defaultLabels);
+ if ($serviceLabels->count() > 0) {
+ $isContainerLabelEscapeEnabled = data_get($resource, 'is_container_label_escape_enabled');
+ if ($isContainerLabelEscapeEnabled) {
+ $serviceLabels = $serviceLabels->map(function ($value, $key) {
+ return escapeDollarSign($value);
+ });
+ }
+ }
+ if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
+ $shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels;
+ $uuid = $resource->uuid;
+ $network = data_get($resource, 'destination.network');
+ if ($shouldGenerateLabelsExactly) {
+ switch ($server->proxyType()) {
+ case ProxyTypes::TRAEFIK->value:
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
+ uuid: $uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $originalResource->isGzipEnabled(),
+ is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: $image
+ ));
+ break;
+ case ProxyTypes::CADDY->value:
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
+ network: $network,
+ uuid: $uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $originalResource->isGzipEnabled(),
+ is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: $image,
+ predefinedPort: $predefinedPort
+ ));
+ break;
+ }
+ } else {
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
+ uuid: $uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $originalResource->isGzipEnabled(),
+ is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: $image
+ ));
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
+ network: $network,
+ uuid: $uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $originalResource->isGzipEnabled(),
+ is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: $image,
+ predefinedPort: $predefinedPort
+ ));
+ }
+ }
+ if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) {
+ $savedService->update(['exclude_from_status' => true]);
+ }
+ data_forget($service, 'volumes.*.content');
+ data_forget($service, 'volumes.*.isDirectory');
+ data_forget($service, 'volumes.*.is_directory');
+ data_forget($service, 'exclude_from_hc');
+
+ $volumesParsed = $volumesParsed->map(function ($volume) {
+ data_forget($volume, 'content');
+ data_forget($volume, 'is_directory');
+ data_forget($volume, 'isDirectory');
+
+ return $volume;
+ });
+
+ $payload = collect($service)->merge([
+ 'container_name' => $containerName,
+ 'restart' => $restart->value(),
+ 'labels' => $serviceLabels,
+ ]);
+ if (! $use_network_mode) {
+ $payload['networks'] = $networks_temp;
+ }
+ if ($ports->count() > 0) {
+ $payload['ports'] = $ports;
+ }
+ if ($volumesParsed->count() > 0) {
+ $payload['volumes'] = $volumesParsed;
+ }
+ if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) {
+ $payload['environment'] = $environment->merge($coolifyEnvironments);
+ }
+ if ($logging) {
+ $payload['logging'] = $logging;
+ }
+ if ($depends_on->count() > 0) {
+ $payload['depends_on'] = $depends_on;
+ }
+
+ $parsedServices->put($serviceName, $payload);
+ }
+ $topLevel->put('services', $parsedServices);
+
+ $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
+
+ $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
+ return array_search($key, $customOrder);
+ });
+
+ $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2);
+ data_forget($resource, 'environment_variables');
+ data_forget($resource, 'environment_variables_preview');
+ $resource->save();
+
+ return $topLevel;
+}
diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php
index cabdabaa7..2d479a193 100644
--- a/bootstrap/helpers/proxy.php
+++ b/bootstrap/helpers/proxy.php
@@ -130,10 +130,16 @@ function generate_default_proxy_configuration(Server $server)
}
$array_of_networks = collect([]);
- $networks->map(function ($network) use ($array_of_networks) {
+ $filtered_networks = collect([]);
+ $networks->map(function ($network) use ($array_of_networks, $filtered_networks) {
+ if ($network === 'host') {
+ return; // network-scoped alias is supported only for containers in user defined networks
+ }
+
$array_of_networks[$network] = [
'external' => true,
];
+ $filtered_networks->push($network);
});
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
$labels = [
@@ -155,7 +161,7 @@ function generate_default_proxy_configuration(Server $server)
'extra_hosts' => [
'host.docker.internal:host-gateway',
],
- 'networks' => $networks->toArray(),
+ 'networks' => $filtered_networks->toArray(),
'ports' => [
'80:80',
'443:443',
@@ -237,7 +243,7 @@ function generate_default_proxy_configuration(Server $server)
'CADDY_DOCKER_POLLING_INTERVAL=5s',
'CADDY_DOCKER_CADDYFILE_PATH=/dynamic/Caddyfile',
],
- 'networks' => $networks->toArray(),
+ 'networks' => $filtered_networks->toArray(),
'ports' => [
'80:80',
'443:443',
diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php
index cd99713a2..cf12a28a5 100644
--- a/bootstrap/helpers/services.php
+++ b/bootstrap/helpers/services.php
@@ -1,7 +1,6 @@
image = $updatedImage;
$resource->save();
}
+
+ $serviceName = str($resource->name)->upper()->replace('-', '_');
+ $resource->service->environment_variables()->where('key', 'LIKE', "SERVICE_FQDN_{$serviceName}%")->delete();
+ $resource->service->environment_variables()->where('key', 'LIKE', "SERVICE_URL_{$serviceName}%")->delete();
+
if ($resource->fqdn) {
$resourceFqdns = str($resource->fqdn)->explode(',');
- if ($resourceFqdns->count() === 1) {
- $resourceFqdns = $resourceFqdns->first();
- $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '');
- $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class)
- ->where('resourceable_id', $resource->service_id)
- ->where('key', $variableName)
- ->first();
- $fqdn = Url::fromString($resourceFqdns);
- $port = $fqdn->getPort();
- $path = $fqdn->getPath();
- $fqdn = $fqdn->getScheme().'://'.$fqdn->getHost();
- if ($generatedEnv) {
- if ($path === '/') {
- $generatedEnv->value = $fqdn;
- } else {
- $generatedEnv->value = $fqdn.$path;
- }
- $generatedEnv->save();
- }
- if ($port) {
- $variableName = $variableName."_$port";
- $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class)
- ->where('resourceable_id', $resource->service_id)
- ->where('key', $variableName)
- ->first();
- if ($generatedEnv) {
- if ($path === '/') {
- $generatedEnv->value = $fqdn;
- } else {
- $generatedEnv->value = $fqdn.$path;
- }
- $generatedEnv->save();
- }
- }
- $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '');
- $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class)
- ->where('resourceable_id', $resource->service_id)
- ->where('key', $variableName)
- ->first();
- $url = Url::fromString($fqdn);
- $port = $url->getPort();
- $path = $url->getPath();
- $url = $url->getHost();
- if ($generatedEnv) {
- $url = str($fqdn)->after('://');
- if ($path === '/') {
- $generatedEnv->value = $url;
- } else {
- $generatedEnv->value = $url.$path;
- }
- $generatedEnv->save();
- }
- if ($port) {
- $variableName = $variableName."_$port";
- $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class)
- ->where('resourceable_id', $resource->service_id)
- ->where('key', $variableName)
- ->first();
- if ($generatedEnv) {
- if ($path === '/') {
- $generatedEnv->value = $url;
- } else {
- $generatedEnv->value = $url.$path;
- }
- $generatedEnv->save();
- }
- }
- } elseif ($resourceFqdns->count() > 1) {
- foreach ($resourceFqdns as $fqdn) {
- $host = Url::fromString($fqdn);
- $port = $host->getPort();
- $url = $host->getHost();
- $path = $host->getPath();
- $host = $host->getScheme().'://'.$host->getHost();
- if ($port) {
- $port_envs = EnvironmentVariable::where('resourceable_type', Service::class)
- ->where('resourceable_id', $resource->service_id)
- ->where('key', 'like', "SERVICE_FQDN_%_$port")
- ->get();
- foreach ($port_envs as $port_env) {
- $service_fqdn = str($port_env->key)->beforeLast('_')->after('SERVICE_FQDN_');
- $env = EnvironmentVariable::where('resourceable_type', Service::class)
- ->where('resourceable_id', $resource->service_id)
- ->where('key', 'SERVICE_FQDN_'.$service_fqdn)
- ->first();
- if ($env) {
- if ($path === '/') {
- $env->value = $host;
- } else {
- $env->value = $host.$path;
- }
- $env->save();
- }
- if ($path === '/') {
- $port_env->value = $host;
- } else {
- $port_env->value = $host.$path;
- }
- $port_env->save();
- }
- $port_envs_url = EnvironmentVariable::where('resourceable_type', Service::class)
- ->where('resourceable_id', $resource->service_id)
- ->where('key', 'like', "SERVICE_URL_%_$port")
- ->get();
- foreach ($port_envs_url as $port_env_url) {
- $service_url = str($port_env_url->key)->beforeLast('_')->after('SERVICE_URL_');
- $env = EnvironmentVariable::where('resourceable_type', Service::class)
- ->where('resourceable_id', $resource->service_id)
- ->where('key', 'SERVICE_URL_'.$service_url)
- ->first();
- if ($env) {
- if ($path === '/') {
- $env->value = $url;
- } else {
- $env->value = $url.$path;
- }
- $env->save();
- }
- if ($path === '/') {
- $port_env_url->value = $url;
- } else {
- $port_env_url->value = $url.$path;
- }
- $port_env_url->save();
- }
- } else {
- $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '');
- $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class)
- ->where('resourceable_id', $resource->service_id)
- ->where('key', $variableName)
- ->first();
- $fqdn = Url::fromString($fqdn);
- $fqdn = $fqdn->getScheme().'://'.$fqdn->getHost().$fqdn->getPath();
- if ($generatedEnv) {
- $generatedEnv->value = $fqdn;
- $generatedEnv->save();
- }
- $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '');
- $generatedEnv = EnvironmentVariable::where('resourceable_type', Service::class)
- ->where('resourceable_id', $resource->service_id)
- ->where('key', $variableName)
- ->first();
- $url = Url::fromString($fqdn);
- $url = $url->getHost().$url->getPath();
- if ($generatedEnv) {
- $url = str($fqdn)->after('://');
- $generatedEnv->value = $url;
- $generatedEnv->save();
- }
- }
- }
+ $resourceFqdns = $resourceFqdns->first();
+ $variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_');
+ $url = Url::fromString($resourceFqdns);
+ $port = $url->getPort();
+ $path = $url->getPath();
+ $urlValue = $url->getScheme().'://'.$url->getHost();
+ $urlValue = ($path === '/') ? $urlValue : $urlValue.$path;
+ $resource->service->environment_variables()->updateOrCreate([
+ 'resourceable_type' => Service::class,
+ 'resourceable_id' => $resource->service_id,
+ 'key' => $variableName,
+ ], [
+ 'value' => $urlValue,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ if ($port) {
+ $variableName = $variableName."_$port";
+ $resource->service->environment_variables()->updateOrCreate([
+ 'resourceable_type' => Service::class,
+ 'resourceable_id' => $resource->service_id,
+ 'key' => $variableName,
+ ], [
+ 'value' => $urlValue,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ }
+ $variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '_');
+ $fqdn = Url::fromString($resourceFqdns);
+ $port = $fqdn->getPort();
+ $path = $fqdn->getPath();
+ $fqdn = $fqdn->getHost();
+ $fqdnValue = str($fqdn)->after('://');
+ if ($path !== '/') {
+ $fqdnValue = $fqdnValue.$path;
+ }
+ $resource->service->environment_variables()->updateOrCreate([
+ 'resourceable_type' => Service::class,
+ 'resourceable_id' => $resource->service_id,
+ 'key' => $variableName,
+ ], [
+ 'value' => $fqdnValue,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ if ($port) {
+ $variableName = $variableName."_$port";
+ $resource->service->environment_variables()->updateOrCreate([
+ 'resourceable_type' => Service::class,
+ 'resourceable_id' => $resource->service_id,
+ 'key' => $variableName,
+ ], [
+ 'value' => $fqdnValue,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
}
}
} catch (\Throwable $e) {
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index 00a674eeb..88bcd5538 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -402,7 +402,7 @@ function data_get_str($data, $key, $default = null): Stringable
return str($str);
}
-function generateFqdn(Server $server, string $random, bool $forceHttps = false): string
+function generateUrl(Server $server, string $random, bool $forceHttps = false): string
{
$wildcard = data_get($server, 'settings.wildcard_domain');
if (is_null($wildcard) || $wildcard === '') {
@@ -418,6 +418,26 @@ function generateFqdn(Server $server, string $random, bool $forceHttps = false):
return "$scheme://{$random}.$host$path";
}
+function generateFqdn(Server $server, string $random, bool $forceHttps = false, int $parserVersion = 4): string
+{
+ $wildcard = data_get($server, 'settings.wildcard_domain');
+ if (is_null($wildcard) || $wildcard === '') {
+ $wildcard = sslip($server);
+ }
+ $url = Url::fromString($wildcard);
+ $host = $url->getHost();
+ $path = $url->getPath() === '/' ? '' : $url->getPath();
+ $scheme = $url->getScheme();
+ if ($forceHttps) {
+ $scheme = 'https';
+ }
+
+ if ($parserVersion >= 5 && version_compare(config('constants.coolify.version'), '4.0.0-beta.420.7', '>=')) {
+ return "{$random}.$host$path";
+ }
+
+ return "$scheme://{$random}.$host$path";
+}
function sslip(Server $server)
{
if (isDev() && $server->id === 0) {
@@ -451,12 +471,12 @@ function get_service_templates(bool $force = false): Collection
return collect($services);
} catch (\Throwable) {
- $services = File::get(base_path('templates/service-templates.json'));
+ $services = File::get(base_path('templates/'.config('constants.services.file_name')));
return collect(json_decode($services))->sortKeys();
}
} else {
- $services = File::get(base_path('templates/service-templates.json'));
+ $services = File::get(base_path('templates/'.config('constants.services.file_name')));
return collect(json_decode($services))->sortKeys();
}
@@ -1005,6 +1025,64 @@ function ip_match($ip, $cidrs, &$match = null)
return false;
}
+
+function check_ip_against_allowlist($ip, $allowlist)
+{
+ if (empty($allowlist)) {
+ return false;
+ }
+
+ foreach ((array) $allowlist as $allowed) {
+ $allowed = trim($allowed);
+
+ if (empty($allowed)) {
+ continue;
+ }
+
+ // Check if it's a CIDR notation
+ if (str_contains($allowed, '/')) {
+ [$subnet, $mask] = explode('/', $allowed);
+
+ // Special case: 0.0.0.0 with any subnet means allow all
+ if ($subnet === '0.0.0.0') {
+ return true;
+ }
+
+ $mask = (int) $mask;
+
+ // Validate mask
+ if ($mask < 0 || $mask > 32) {
+ continue;
+ }
+
+ // Calculate network addresses
+ $ip_long = ip2long($ip);
+ $subnet_long = ip2long($subnet);
+
+ if ($ip_long === false || $subnet_long === false) {
+ continue;
+ }
+
+ $mask_long = ~((1 << (32 - $mask)) - 1);
+
+ if (($ip_long & $mask_long) == ($subnet_long & $mask_long)) {
+ return true;
+ }
+ } else {
+ // Special case: 0.0.0.0 means allow all
+ if ($allowed === '0.0.0.0') {
+ return true;
+ }
+
+ // Direct IP comparison
+ if ($ip === $allowed) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = null, ?string $uuid = null)
{
if (is_null($teamId)) {
@@ -1306,143 +1384,6 @@ function customApiValidator(Collection|array $item, array $rules)
'required' => 'This field is required.',
]);
}
-
-function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull_request_id = 0)
-{
- $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
- $type = null;
- $source = null;
- $target = null;
- $content = null;
- $isDirectory = false;
- if (is_string($volume)) {
- $source = str($volume)->before(':');
- $target = str($volume)->after(':')->beforeLast(':');
- $foundConfig = $resource->fileStorages()->whereMountPath($target)->first();
- if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) {
- $type = str('bind');
- if ($foundConfig) {
- $contentNotNull = data_get($foundConfig, 'content');
- if ($contentNotNull) {
- $content = $contentNotNull;
- }
- $isDirectory = data_get($foundConfig, 'is_directory');
- } else {
- // By default, we cannot determine if the bind is a directory or not, so we set it to directory
- $isDirectory = true;
- }
- } else {
- $type = str('volume');
- }
- } elseif (is_array($volume)) {
- $type = data_get_str($volume, 'type');
- $source = data_get_str($volume, 'source');
- $target = data_get_str($volume, 'target');
- $content = data_get($volume, 'content');
- $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
- $foundConfig = $resource->fileStorages()->whereMountPath($target)->first();
- if ($foundConfig) {
- $contentNotNull = data_get($foundConfig, 'content');
- if ($contentNotNull) {
- $content = $contentNotNull;
- }
- $isDirectory = data_get($foundConfig, 'is_directory');
- } else {
- $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
- if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
- // if isDirectory is not set (or false) & content is also not set, we assume it is a directory
- $isDirectory = true;
- }
- }
- }
- if ($type?->value() === 'bind') {
- if ($source->value() === '/var/run/docker.sock') {
- return $volume;
- }
- if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
- return $volume;
- }
- if (get_class($resource) === \App\Models\Application::class) {
- $dir = base_configuration_dir().'/applications/'.$resource->uuid;
- } else {
- $dir = base_configuration_dir().'/services/'.$resource->service->uuid;
- }
-
- if ($source->startsWith('.')) {
- $source = $source->replaceFirst('.', $dir);
- }
- if ($source->startsWith('~')) {
- $source = $source->replaceFirst('~', $dir);
- }
- if ($pull_request_id !== 0) {
- $source = $source."-pr-$pull_request_id";
- }
- if (! $resource?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git) {
- LocalFileVolume::updateOrCreate(
- [
- 'mount_path' => $target,
- 'resource_id' => $resource->id,
- 'resource_type' => get_class($resource),
- ],
- [
- 'fs_path' => $source,
- 'mount_path' => $target,
- 'content' => $content,
- 'is_directory' => $isDirectory,
- 'resource_id' => $resource->id,
- 'resource_type' => get_class($resource),
- ]
- );
- }
- } elseif ($type->value() === 'volume') {
- if ($topLevelVolumes->has($source->value())) {
- $v = $topLevelVolumes->get($source->value());
- if (data_get($v, 'driver_opts.type') === 'cifs') {
- return $volume;
- }
- }
- $slugWithoutUuid = Str::slug($source, '-');
- if (get_class($resource) === \App\Models\Application::class) {
- $name = "{$resource->uuid}_{$slugWithoutUuid}";
- } else {
- $name = "{$resource->service->uuid}_{$slugWithoutUuid}";
- }
- if (is_string($volume)) {
- $source = str($volume)->before(':');
- $target = str($volume)->after(':')->beforeLast(':');
- $source = $name;
- $volume = "$source:$target";
- } elseif (is_array($volume)) {
- data_set($volume, 'source', $name);
- }
- $topLevelVolumes->put($name, [
- 'name' => $name,
- ]);
- LocalPersistentVolume::updateOrCreate(
- [
- 'mount_path' => $target,
- 'resource_id' => $resource->id,
- 'resource_type' => get_class($resource),
- ],
- [
- 'name' => $name,
- 'mount_path' => $target,
- 'resource_id' => $resource->id,
- 'resource_type' => get_class($resource),
- ]
- );
- }
- dispatch(new ServerFilesFromServerJob($resource));
-
- return $volume;
- });
-
- return [
- 'serviceVolumes' => $serviceVolumes,
- 'topLevelVolumes' => $topLevelVolumes,
- ];
-}
-
function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, ?int $preview_id = null)
{
if ($resource->getMorphClass() === \App\Models\Service::class) {
@@ -2918,1005 +2859,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
}
-function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $preview_id = null): Collection
-{
- $isApplication = $resource instanceof Application;
- $isService = $resource instanceof Service;
-
- $uuid = data_get($resource, 'uuid');
- $compose = data_get($resource, 'docker_compose_raw');
- if (! $compose) {
- return collect([]);
- }
-
- if ($isApplication) {
- $pullRequestId = $pull_request_id;
- $isPullRequest = $pullRequestId == 0 ? false : true;
- $server = data_get($resource, 'destination.server');
- $fileStorages = $resource->fileStorages();
- } elseif ($isService) {
- $server = data_get($resource, 'server');
- $allServices = get_service_templates();
- } else {
- return collect([]);
- }
-
- try {
- $yaml = Yaml::parse($compose);
- } catch (\Exception) {
- return collect([]);
- }
- $services = data_get($yaml, 'services', collect([]));
- $topLevel = collect([
- 'volumes' => collect(data_get($yaml, 'volumes', [])),
- 'networks' => collect(data_get($yaml, 'networks', [])),
- 'configs' => collect(data_get($yaml, 'configs', [])),
- 'secrets' => collect(data_get($yaml, 'secrets', [])),
- ]);
- // If there are predefined volumes, make sure they are not null
- if ($topLevel->get('volumes')->count() > 0) {
- $temp = collect([]);
- foreach ($topLevel['volumes'] as $volumeName => $volume) {
- if (is_null($volume)) {
- continue;
- }
- $temp->put($volumeName, $volume);
- }
- $topLevel['volumes'] = $temp;
- }
- // Get the base docker network
- $baseNetwork = collect([$uuid]);
- if ($isApplication && $isPullRequest) {
- $baseNetwork = collect(["{$uuid}-{$pullRequestId}"]);
- }
-
- $parsedServices = collect([]);
-
- $allMagicEnvironments = collect([]);
- foreach ($services as $serviceName => $service) {
- $predefinedPort = null;
- $magicEnvironments = collect([]);
- $image = data_get_str($service, 'image');
- $environment = collect(data_get($service, 'environment', []));
- $buildArgs = collect(data_get($service, 'build.args', []));
- $environment = $environment->merge($buildArgs);
- $isDatabase = isDatabaseImage($image, $service);
-
- if ($isService) {
- $containerName = "$serviceName-{$resource->uuid}";
-
- if ($serviceName === 'registry') {
- $tempServiceName = 'docker-registry';
- } else {
- $tempServiceName = $serviceName;
- }
- if (str(data_get($service, 'image'))->contains('glitchtip')) {
- $tempServiceName = 'glitchtip';
- }
- if ($serviceName === 'supabase-kong') {
- $tempServiceName = 'supabase';
- }
- $serviceDefinition = data_get($allServices, $tempServiceName);
- $predefinedPort = data_get($serviceDefinition, 'port');
- if ($serviceName === 'plausible') {
- $predefinedPort = '8000';
- }
- if ($isDatabase) {
- $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first();
- if ($applicationFound) {
- $savedService = $applicationFound;
- } else {
- $savedService = ServiceDatabase::firstOrCreate([
- 'name' => $serviceName,
- 'service_id' => $resource->id,
- ]);
- }
- } else {
- $savedService = ServiceApplication::firstOrCreate([
- 'name' => $serviceName,
- 'service_id' => $resource->id,
- ], [
- 'is_gzip_enabled' => true,
- ]);
- }
- // Check if image changed
- if ($savedService->image !== $image) {
- $savedService->image = $image;
- $savedService->save();
- }
- // Pocketbase does not need gzip for SSE.
- if (str($savedService->image)->contains('pocketbase') && $savedService->is_gzip_enabled) {
- $savedService->is_gzip_enabled = false;
- $savedService->save();
- }
- }
-
- $environment = collect(data_get($service, 'environment', []));
- $buildArgs = collect(data_get($service, 'build.args', []));
- $environment = $environment->merge($buildArgs);
-
- // convert environment variables to one format
- $environment = convertToKeyValueCollection($environment);
-
- // Add Coolify defined environments
- $allEnvironments = $resource->environment_variables()->get(['key', 'value']);
-
- $allEnvironments = $allEnvironments->mapWithKeys(function ($item) {
- return [$item['key'] => $item['value']];
- });
- // filter and add magic environments
- foreach ($environment as $key => $value) {
- // Get all SERVICE_ variables from keys and values
- $key = str($key);
- $value = str($value);
-
- $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/';
- preg_match_all($regex, $value, $valueMatches);
- if (count($valueMatches[1]) > 0) {
- foreach ($valueMatches[1] as $match) {
- $match = replaceVariables($match);
- if ($match->startsWith('SERVICE_')) {
- if ($magicEnvironments->has($match->value())) {
- continue;
- }
- $magicEnvironments->put($match->value(), '');
- }
- }
- }
- // Get magic environments where we need to preset the FQDN
- if ($key->startsWith('SERVICE_FQDN_')) {
- // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
- if (substr_count(str($key)->value(), '_') === 3) {
- $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value();
- $port = $key->afterLast('_')->value();
- } else {
- $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
- $port = null;
- }
- if ($isApplication) {
- $fqdn = $resource->fqdn;
- if (blank($resource->fqdn)) {
- $fqdn = generateFqdn($server, "$uuid");
- }
- } elseif ($isService) {
- if (blank($savedService->fqdn)) {
- if ($fqdnFor) {
- $fqdn = generateFqdn($server, "$fqdnFor-$uuid");
- } else {
- $fqdn = generateFqdn($server, "{$savedService->name}-$uuid");
- }
- } else {
- $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value();
- }
- }
-
- if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) {
- $path = $value->value();
- if ($path !== '/') {
- $fqdn = "$fqdn$path";
- }
- }
- $fqdnWithPort = $fqdn;
- if ($port) {
- $fqdnWithPort = "$fqdn:$port";
- }
- if ($isApplication && is_null($resource->fqdn)) {
- data_forget($resource, 'environment_variables');
- data_forget($resource, 'environment_variables_preview');
- $resource->fqdn = $fqdnWithPort;
- $resource->save();
- } elseif ($isService && is_null($savedService->fqdn)) {
- $savedService->fqdn = $fqdnWithPort;
- $savedService->save();
- }
-
- if (substr_count(str($key)->value(), '_') === 2) {
- $resource->environment_variables()->updateOrCreate([
- 'key' => $key->value(),
- 'resourceable_type' => get_class($resource),
- 'resourceable_id' => $resource->id,
- ], [
- 'value' => $fqdn,
- 'is_build_time' => false,
- 'is_preview' => false,
- ]);
- }
- if (substr_count(str($key)->value(), '_') === 3) {
- $newKey = str($key)->beforeLast('_');
- $resource->environment_variables()->updateOrCreate([
- 'key' => $newKey->value(),
- 'resourceable_type' => get_class($resource),
- 'resourceable_id' => $resource->id,
- ], [
- 'value' => $fqdn,
- 'is_build_time' => false,
- 'is_preview' => false,
- ]);
- }
- }
- }
-
- $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
- if ($magicEnvironments->count() > 0) {
- foreach ($magicEnvironments as $key => $value) {
- $key = str($key);
- $value = replaceVariables($value);
- $command = parseCommandFromMagicEnvVariable($key);
- $found = $resource->environment_variables()->where('key', $key->value())->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first();
- if ($found) {
- continue;
- }
- if ($command->value() === 'FQDN') {
- if ($isApplication && $resource->build_pack === 'dockercompose') {
- continue;
- }
- $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
- if (str($fqdnFor)->contains('_')) {
- $fqdnFor = str($fqdnFor)->before('_');
- }
- $fqdn = generateFqdn($server, "$fqdnFor-$uuid");
- $resource->environment_variables()->firstOrCreate([
- 'key' => $key->value(),
- 'resourceable_type' => get_class($resource),
- 'resourceable_id' => $resource->id,
- ], [
- 'value' => $fqdn,
- 'is_build_time' => false,
- 'is_preview' => false,
- ]);
- } elseif ($command->value() === 'URL') {
- if ($isApplication && $resource->build_pack === 'dockercompose') {
- continue;
- }
- $fqdnFor = $key->after('SERVICE_URL_')->lower()->value();
- if (str($fqdnFor)->contains('_')) {
- $fqdnFor = str($fqdnFor)->before('_');
- }
- $fqdn = generateFqdn($server, "$fqdnFor-$uuid");
- $fqdn = str($fqdn)->replace('http://', '')->replace('https://', '');
- $resource->environment_variables()->firstOrCreate([
- 'key' => $key->value(),
- 'resourceable_type' => get_class($resource),
- 'resourceable_id' => $resource->id,
- ], [
- 'value' => $fqdn,
- 'is_build_time' => false,
- 'is_preview' => false,
- ]);
- } else {
- $value = generateEnvValue($command, $resource);
- $resource->environment_variables()->firstOrCreate([
- 'key' => $key->value(),
- 'resourceable_type' => get_class($resource),
- 'resourceable_id' => $resource->id,
- ], [
- 'value' => $value,
- 'is_build_time' => false,
- 'is_preview' => false,
- ]);
- }
- }
- }
- }
-
- $serviceAppsLogDrainEnabledMap = collect([]);
- if ($resource instanceof Service) {
- $serviceAppsLogDrainEnabledMap = $resource->applications()->get()->keyBy('name')->map(function ($app) {
- return $app->isLogDrainEnabled();
- });
- }
-
- // Parse the rest of the services
- foreach ($services as $serviceName => $service) {
- $image = data_get_str($service, 'image');
- $restart = data_get_str($service, 'restart', RESTART_MODE);
- $logging = data_get($service, 'logging');
-
- if ($server->isLogDrainEnabled()) {
- if ($resource instanceof Application && $resource->isLogDrainEnabled()) {
- $logging = generate_fluentd_configuration();
- }
- if ($resource instanceof Service && $serviceAppsLogDrainEnabledMap->get($serviceName)) {
- $logging = generate_fluentd_configuration();
- }
- }
- $volumes = collect(data_get($service, 'volumes', []));
- $networks = collect(data_get($service, 'networks', []));
- $use_network_mode = data_get($service, 'network_mode') !== null;
- $depends_on = collect(data_get($service, 'depends_on', []));
- $labels = collect(data_get($service, 'labels', []));
- if ($labels->count() > 0) {
- if (isAssociativeArray($labels)) {
- $newLabels = collect([]);
- $labels->each(function ($value, $key) use ($newLabels) {
- $newLabels->push("$key=$value");
- });
- $labels = $newLabels;
- }
- }
- $environment = collect(data_get($service, 'environment', []));
- $ports = collect(data_get($service, 'ports', []));
- $buildArgs = collect(data_get($service, 'build.args', []));
- $environment = $environment->merge($buildArgs);
-
- $environment = convertToKeyValueCollection($environment);
- $coolifyEnvironments = collect([]);
-
- $isDatabase = isDatabaseImage($image, $service);
- $volumesParsed = collect([]);
-
- if ($isApplication) {
- $baseName = generateApplicationContainerName(
- application: $resource,
- pull_request_id: $pullRequestId
- );
- $containerName = "$serviceName-$baseName";
- $predefinedPort = null;
- } elseif ($isService) {
- $containerName = "$serviceName-{$resource->uuid}";
-
- if ($serviceName === 'registry') {
- $tempServiceName = 'docker-registry';
- } else {
- $tempServiceName = $serviceName;
- }
- if (str(data_get($service, 'image'))->contains('glitchtip')) {
- $tempServiceName = 'glitchtip';
- }
- if ($serviceName === 'supabase-kong') {
- $tempServiceName = 'supabase';
- }
- $serviceDefinition = data_get($allServices, $tempServiceName);
- $predefinedPort = data_get($serviceDefinition, 'port');
- if ($serviceName === 'plausible') {
- $predefinedPort = '8000';
- }
-
- if ($isDatabase) {
- $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
- if ($applicationFound) {
- $savedService = $applicationFound;
- // $savedService = ServiceDatabase::firstOrCreate([
- // 'name' => $applicationFound->name,
- // 'image' => $applicationFound->image,
- // 'service_id' => $applicationFound->service_id,
- // ]);
- // $applicationFound->delete();
- } else {
- $savedService = ServiceDatabase::firstOrCreate([
- 'name' => $serviceName,
- 'image' => $image,
- 'service_id' => $resource->id,
- ]);
- }
- } else {
- $savedService = ServiceApplication::firstOrCreate([
- 'name' => $serviceName,
- 'image' => $image,
- 'service_id' => $resource->id,
- ]);
- }
- $fileStorages = $savedService->fileStorages();
- if ($savedService->image !== $image) {
- $savedService->image = $image;
- $savedService->save();
- }
- }
-
- $originalResource = $isApplication ? $resource : $savedService;
-
- if ($volumes->count() > 0) {
- foreach ($volumes as $index => $volume) {
- $type = null;
- $source = null;
- $target = null;
- $content = null;
- $isDirectory = false;
- if (is_string($volume)) {
- $source = str($volume)->before(':');
- $target = str($volume)->after(':')->beforeLast(':');
- $foundConfig = $fileStorages->whereMountPath($target)->first();
- if (sourceIsLocal($source)) {
- $type = str('bind');
- if ($foundConfig) {
- $contentNotNull_temp = data_get($foundConfig, 'content');
- if ($contentNotNull_temp) {
- $content = $contentNotNull_temp;
- }
- $isDirectory = data_get($foundConfig, 'is_directory');
- } else {
- // By default, we cannot determine if the bind is a directory or not, so we set it to directory
- $isDirectory = true;
- }
- } else {
- $type = str('volume');
- }
- } elseif (is_array($volume)) {
- $type = data_get_str($volume, 'type');
- $source = data_get_str($volume, 'source');
- $target = data_get_str($volume, 'target');
- $content = data_get($volume, 'content');
- $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
-
- $foundConfig = $fileStorages->whereMountPath($target)->first();
- if ($foundConfig) {
- $contentNotNull_temp = data_get($foundConfig, 'content');
- if ($contentNotNull_temp) {
- $content = $contentNotNull_temp;
- }
- $isDirectory = data_get($foundConfig, 'is_directory');
- } else {
- // if isDirectory is not set (or false) & content is also not set, we assume it is a directory
- if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
- $isDirectory = true;
- }
- }
- }
- if ($type->value() === 'bind') {
- if ($source->value() === '/var/run/docker.sock') {
- $volume = $source->value().':'.$target->value();
- } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') {
- $volume = $source->value().':'.$target->value();
- } else {
- if ((int) $resource->compose_parsing_version >= 4) {
- if ($isApplication) {
- $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
- } elseif ($isService) {
- $mainDirectory = str(base_configuration_dir().'/services/'.$uuid);
- }
- } else {
- $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
- }
- $source = replaceLocalSource($source, $mainDirectory);
- if ($isApplication && $isPullRequest) {
- $source = $source."-pr-$pullRequestId";
- }
- LocalFileVolume::updateOrCreate(
- [
- 'mount_path' => $target,
- 'resource_id' => $originalResource->id,
- 'resource_type' => get_class($originalResource),
- ],
- [
- 'fs_path' => $source,
- 'mount_path' => $target,
- 'content' => $content,
- 'is_directory' => $isDirectory,
- 'resource_id' => $originalResource->id,
- 'resource_type' => get_class($originalResource),
- ]
- );
- if (isDev()) {
- if ((int) $resource->compose_parsing_version >= 4) {
- if ($isApplication) {
- $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
- } elseif ($isService) {
- $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid);
- }
- } else {
- $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
- }
- }
- $volume = "$source:$target";
- }
- } elseif ($type->value() === 'volume') {
- if ($topLevel->get('volumes')->has($source->value())) {
- $temp = $topLevel->get('volumes')->get($source->value());
- if (data_get($temp, 'driver_opts.type') === 'cifs') {
- continue;
- }
- if (data_get($temp, 'driver_opts.type') === 'nfs') {
- continue;
- }
- }
- $slugWithoutUuid = Str::slug($source, '-');
- $name = "{$uuid}_{$slugWithoutUuid}";
-
- if ($isApplication && $isPullRequest) {
- $name = "{$name}-pr-$pullRequestId";
- }
- if (is_string($volume)) {
- $source = str($volume)->before(':');
- $target = str($volume)->after(':')->beforeLast(':');
- $source = $name;
- $volume = "$source:$target";
- } elseif (is_array($volume)) {
- data_set($volume, 'source', $name);
- }
- $topLevel->get('volumes')->put($name, [
- 'name' => $name,
- ]);
- LocalPersistentVolume::updateOrCreate(
- [
- 'name' => $name,
- 'resource_id' => $originalResource->id,
- 'resource_type' => get_class($originalResource),
- ],
- [
- 'name' => $name,
- 'mount_path' => $target,
- 'resource_id' => $originalResource->id,
- 'resource_type' => get_class($originalResource),
- ]
- );
- }
- dispatch(new ServerFilesFromServerJob($originalResource));
- $volumesParsed->put($index, $volume);
- }
- }
-
- if ($depends_on?->count() > 0) {
- if ($isApplication && $isPullRequest) {
- $newDependsOn = collect([]);
- $depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) {
- if (is_numeric($condition)) {
- $dependency = "$dependency-pr-$pullRequestId";
-
- $newDependsOn->put($condition, $dependency);
- } else {
- $condition = "$condition-pr-$pullRequestId";
- $newDependsOn->put($condition, $dependency);
- }
- });
- $depends_on = $newDependsOn;
- }
- }
- if (! $use_network_mode) {
- if ($topLevel->get('networks')?->count() > 0) {
- foreach ($topLevel->get('networks') as $networkName => $network) {
- if ($networkName === 'default') {
- continue;
- }
- // ignore aliases
- if ($network['aliases'] ?? false) {
- continue;
- }
- $networkExists = $networks->contains(function ($value, $key) use ($networkName) {
- return $value == $networkName || $key == $networkName;
- });
- if (! $networkExists) {
- $networks->put($networkName, null);
- }
- }
- }
- $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) {
- return $value == $baseNetwork;
- });
- if (! $baseNetworkExists) {
- foreach ($baseNetwork as $network) {
- $topLevel->get('networks')->put($network, [
- 'name' => $network,
- 'external' => true,
- ]);
- }
- }
- }
-
- // Collect/create/update ports
- $collectedPorts = collect([]);
- if ($ports->count() > 0) {
- foreach ($ports as $sport) {
- if (is_string($sport) || is_numeric($sport)) {
- $collectedPorts->push($sport);
- }
- if (is_array($sport)) {
- $target = data_get($sport, 'target');
- $published = data_get($sport, 'published');
- $protocol = data_get($sport, 'protocol');
- $collectedPorts->push("$target:$published/$protocol");
- }
- }
- }
- if ($isService) {
- $originalResource->ports = $collectedPorts->implode(',');
- $originalResource->save();
- }
-
- $networks_temp = collect();
-
- if (! $use_network_mode) {
- foreach ($networks as $key => $network) {
- if (gettype($network) === 'string') {
- // networks:
- // - appwrite
- $networks_temp->put($network, null);
- } elseif (gettype($network) === 'array') {
- // networks:
- // default:
- // ipv4_address: 192.168.203.254
- $networks_temp->put($key, $network);
- }
- }
- foreach ($baseNetwork as $key => $network) {
- $networks_temp->put($network, null);
- }
-
- if ($isApplication) {
- if (data_get($resource, 'settings.connect_to_docker_network')) {
- $network = $resource->destination->network;
- $networks_temp->put($network, null);
- $topLevel->get('networks')->put($network, [
- 'name' => $network,
- 'external' => true,
- ]);
- }
- }
- }
-
- $normalEnvironments = $environment->diffKeys($allMagicEnvironments);
- $normalEnvironments = $normalEnvironments->filter(function ($value, $key) {
- return ! str($value)->startsWith('SERVICE_');
- });
-
- foreach ($normalEnvironments as $key => $value) {
- $key = str($key);
- $value = str($value);
- $originalValue = $value;
- $parsedValue = replaceVariables($value);
- if ($value->startsWith('$SERVICE_')) {
- $resource->environment_variables()->firstOrCreate([
- 'key' => $key,
- 'resourceable_type' => get_class($resource),
- 'resourceable_id' => $resource->id,
- ], [
- 'value' => $value,
- 'is_build_time' => false,
- 'is_preview' => false,
- ]);
-
- continue;
- }
- if (! $value->startsWith('$')) {
- continue;
- }
- if ($key->value() === $parsedValue->value()) {
- $value = null;
- $resource->environment_variables()->firstOrCreate([
- 'key' => $key,
- 'resourceable_type' => get_class($resource),
- 'resourceable_id' => $resource->id,
- ], [
- 'value' => $value,
- 'is_build_time' => false,
- 'is_preview' => false,
- ]);
- } else {
- if ($value->startsWith('$')) {
- $isRequired = false;
- if ($value->contains(':-')) {
- $value = replaceVariables($value);
- $key = $value->before(':');
- $value = $value->after(':-');
- } elseif ($value->contains('-')) {
- $value = replaceVariables($value);
-
- $key = $value->before('-');
- $value = $value->after('-');
- } elseif ($value->contains(':?')) {
- $value = replaceVariables($value);
-
- $key = $value->before(':');
- $value = $value->after(':?');
- $isRequired = true;
- } elseif ($value->contains('?')) {
- $value = replaceVariables($value);
-
- $key = $value->before('?');
- $value = $value->after('?');
- $isRequired = true;
- }
- if ($originalValue->value() === $value->value()) {
- // This means the variable does not have a default value, so it needs to be created in Coolify
- $parsedKeyValue = replaceVariables($value);
- $resource->environment_variables()->firstOrCreate([
- 'key' => $parsedKeyValue,
- 'resourceable_type' => get_class($resource),
- 'resourceable_id' => $resource->id,
- ], [
- 'is_build_time' => false,
- 'is_preview' => false,
- 'is_required' => $isRequired,
- ]);
- // Add the variable to the environment so it will be shown in the deployable compose file
- // $environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first()->real_value;
- $environment[$parsedKeyValue->value()] = $value;
-
- continue;
- }
- $resource->environment_variables()->firstOrCreate([
- 'key' => $key,
- 'resourceable_type' => get_class($resource),
- 'resourceable_id' => $resource->id,
- ], [
- 'value' => $value,
- 'is_build_time' => false,
- 'is_preview' => false,
- 'is_required' => $isRequired,
- ]);
- }
- }
- }
- if ($isApplication) {
- $branch = $originalResource->git_branch;
- if ($pullRequestId !== 0) {
- $branch = "pull/{$pullRequestId}/head";
- }
- if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
- $coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\"");
- }
- }
-
- // Add COOLIFY_RESOURCE_UUID to environment
- if ($resource->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) {
- $coolifyEnvironments->put('COOLIFY_RESOURCE_UUID', "{$resource->uuid}");
- }
-
- // Add COOLIFY_CONTAINER_NAME to environment
- if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
- $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "{$containerName}");
- }
-
- if ($isApplication) {
- if ($isPullRequest) {
- $preview = $resource->previews()->find($preview_id);
- $domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))) ?? collect([]);
- } else {
- $domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]);
- }
- $fqdns = data_get($domains, "$serviceName.domain");
- // Generate SERVICE_FQDN & SERVICE_URL for dockercompose
- if ($resource->build_pack === 'dockercompose') {
- foreach ($domains as $forServiceName => $domain) {
- $parsedDomain = data_get($domain, 'domain');
- if (filled($parsedDomain)) {
- $parsedDomain = str($parsedDomain)->explode(',')->first();
- $coolifyUrl = Url::fromString($parsedDomain);
- $coolifyScheme = $coolifyUrl->getScheme();
- $coolifyFqdn = $coolifyUrl->getHost();
- $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
- $coolifyEnvironments->put('SERVICE_URL_'.str($forServiceName)->upper(), $coolifyUrl->__toString());
- $coolifyEnvironments->put('SERVICE_FQDN_'.str($forServiceName)->upper(), $coolifyFqdn);
- }
- }
- }
- // If the domain is set, we need to generate the FQDNs for the preview
- if (filled($fqdns)) {
- $fqdns = str($fqdns)->explode(',');
- if ($isPullRequest) {
- $preview = $resource->previews()->find($preview_id);
- $docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains')));
- if ($docker_compose_domains->count() > 0) {
- $found_fqdn = data_get($docker_compose_domains, "$serviceName.domain");
- if ($found_fqdn) {
- $fqdns = collect($found_fqdn);
- } else {
- $fqdns = collect([]);
- }
- } else {
- $fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId, $resource) {
- $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pullRequestId);
- $url = Url::fromString($fqdn);
- $template = $resource->preview_url_template;
- $host = $url->getHost();
- $schema = $url->getScheme();
- $random = new Cuid2;
- $preview_fqdn = str_replace('{{random}}', $random, $template);
- $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
- $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn);
- $preview_fqdn = "$schema://$preview_fqdn";
- $preview->fqdn = $preview_fqdn;
- $preview->save();
-
- return $preview_fqdn;
- });
- }
- }
- }
- $defaultLabels = defaultLabels(
- id: $resource->id,
- name: $containerName,
- projectName: $resource->project()->name,
- resourceName: $resource->name,
- pull_request_id: $pullRequestId,
- type: 'application',
- environment: $resource->environment->name,
- );
-
- } elseif ($isService) {
- if ($savedService->serviceType()) {
- $fqdns = generateServiceSpecificFqdns($savedService);
- } else {
- $fqdns = collect(data_get($savedService, 'fqdns'))->filter();
- }
-
- $defaultLabels = defaultLabels(
- id: $resource->id,
- name: $containerName,
- projectName: $resource->project()->name,
- resourceName: $resource->name,
- type: 'service',
- subType: $isDatabase ? 'database' : 'application',
- subId: $savedService->id,
- subName: $savedService->human_name ?? $savedService->name,
- environment: $resource->environment->name,
- );
- }
- // Add COOLIFY_FQDN & COOLIFY_URL to environment
- if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
- $fqdnsWithoutPort = $fqdns->map(function ($fqdn) {
- return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://'));
- });
- $coolifyEnvironments->put('COOLIFY_URL', $fqdnsWithoutPort->implode(','));
-
- $urls = $fqdns->map(function ($fqdn) {
- return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':');
- });
- $coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(','));
- }
- add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables);
-
- if ($environment->count() > 0) {
- $environment = $environment->filter(function ($value, $key) {
- return ! str($key)->startsWith('SERVICE_FQDN_');
- })->map(function ($value, $key) use ($resource) {
- // if value is empty, set it to null so if you set the environment variable in the .env file (Coolify's UI), it will used
- if (str($value)->isEmpty()) {
- if ($resource->environment_variables()->where('key', $key)->exists()) {
- $value = $resource->environment_variables()->where('key', $key)->first()->value;
- } else {
- $value = null;
- }
- }
-
- return $value;
- });
- }
- $serviceLabels = $labels->merge($defaultLabels);
- if ($serviceLabels->count() > 0) {
- if ($isApplication) {
- $isContainerLabelEscapeEnabled = data_get($resource, 'settings.is_container_label_escape_enabled');
- } else {
- $isContainerLabelEscapeEnabled = data_get($resource, 'is_container_label_escape_enabled');
- }
- if ($isContainerLabelEscapeEnabled) {
- $serviceLabels = $serviceLabels->map(function ($value, $key) {
- return escapeDollarSign($value);
- });
- }
- }
- if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
- if ($isApplication) {
- $shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels;
- $uuid = $resource->uuid;
- $network = data_get($resource, 'destination.network');
- if ($isPullRequest) {
- $uuid = "{$resource->uuid}-{$pullRequestId}";
- }
- if ($isPullRequest) {
- $network = "{$resource->destination->network}-{$pullRequestId}";
- }
- } else {
- $shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels;
- $uuid = $resource->uuid;
- $network = data_get($resource, 'destination.network');
- }
- if ($shouldGenerateLabelsExactly) {
- switch ($server->proxyType()) {
- case ProxyTypes::TRAEFIK->value:
- $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
- uuid: $uuid,
- domains: $fqdns,
- is_force_https_enabled: true,
- serviceLabels: $serviceLabels,
- is_gzip_enabled: $originalResource->isGzipEnabled(),
- is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
- service_name: $serviceName,
- image: $image
- ));
- break;
- case ProxyTypes::CADDY->value:
- $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
- network: $network,
- uuid: $uuid,
- domains: $fqdns,
- is_force_https_enabled: true,
- serviceLabels: $serviceLabels,
- is_gzip_enabled: $originalResource->isGzipEnabled(),
- is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
- service_name: $serviceName,
- image: $image,
- predefinedPort: $predefinedPort
- ));
- break;
- }
- } else {
- $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
- uuid: $uuid,
- domains: $fqdns,
- is_force_https_enabled: true,
- serviceLabels: $serviceLabels,
- is_gzip_enabled: $originalResource->isGzipEnabled(),
- is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
- service_name: $serviceName,
- image: $image
- ));
- $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
- network: $network,
- uuid: $uuid,
- domains: $fqdns,
- is_force_https_enabled: true,
- serviceLabels: $serviceLabels,
- is_gzip_enabled: $originalResource->isGzipEnabled(),
- is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
- service_name: $serviceName,
- image: $image,
- predefinedPort: $predefinedPort
- ));
- }
- }
- if ($isService) {
- if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) {
- $savedService->update(['exclude_from_status' => true]);
- }
- }
- data_forget($service, 'volumes.*.content');
- data_forget($service, 'volumes.*.isDirectory');
- data_forget($service, 'volumes.*.is_directory');
- data_forget($service, 'exclude_from_hc');
-
- $volumesParsed = $volumesParsed->map(function ($volume) {
- data_forget($volume, 'content');
- data_forget($volume, 'is_directory');
- data_forget($volume, 'isDirectory');
-
- return $volume;
- });
-
- $payload = collect($service)->merge([
- 'container_name' => $containerName,
- 'restart' => $restart->value(),
- 'labels' => $serviceLabels,
- ]);
- if (! $use_network_mode) {
- $payload['networks'] = $networks_temp;
- }
- if ($ports->count() > 0) {
- $payload['ports'] = $ports;
- }
- if ($volumesParsed->count() > 0) {
- $payload['volumes'] = $volumesParsed;
- }
- if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) {
- $payload['environment'] = $environment->merge($coolifyEnvironments);
- }
- if ($logging) {
- $payload['logging'] = $logging;
- }
- if ($depends_on->count() > 0) {
- $payload['depends_on'] = $depends_on;
- }
- if ($isApplication && $isPullRequest) {
- $serviceName = "{$serviceName}-pr-{$pullRequestId}";
- }
-
- $parsedServices->put($serviceName, $payload);
- }
- $topLevel->put('services', $parsedServices);
-
- $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
-
- $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
- return array_search($key, $customOrder);
- });
-
- $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2);
- data_forget($resource, 'environment_variables');
- data_forget($resource, 'environment_variables_preview');
- $resource->save();
-
- return $topLevel;
-}
-
function generate_fluentd_configuration(): array
{
return [
diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php
index 510516a2f..48c3a62c3 100644
--- a/bootstrap/helpers/subscriptions.php
+++ b/bootstrap/helpers/subscriptions.php
@@ -89,3 +89,22 @@ function allowedPathsForInvalidAccounts()
'livewire/update',
];
}
+
+function updateStripeCustomerEmail(Team $team, string $newEmail): void
+{
+ if (! isStripe()) {
+ return;
+ }
+
+ $stripe_customer_id = data_get($team, 'subscription.stripe_customer_id');
+ if (! $stripe_customer_id) {
+ return;
+ }
+
+ Stripe::setApiKey(config('subscription.stripe_api_key'));
+
+ \Stripe\Customer::update(
+ $stripe_customer_id,
+ ['email' => $newEmail]
+ );
+}
diff --git a/changelogs/.gitignore b/changelogs/.gitignore
new file mode 100644
index 000000000..d6b7ef32c
--- /dev/null
+++ b/changelogs/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/composer.json b/composer.json
index 7e8e768c0..38756edf9 100644
--- a/composer.json
+++ b/composer.json
@@ -47,6 +47,7 @@
"socialiteproviders/zitadel": "^4.2",
"spatie/laravel-activitylog": "^4.10.2",
"spatie/laravel-data": "^4.17.0",
+ "spatie/laravel-markdown": "^2.7",
"spatie/laravel-ray": "^1.40.2",
"spatie/laravel-schemaless-attributes": "^2.5.1",
"spatie/url": "^2.4",
diff --git a/composer.lock b/composer.lock
index 8d170cdc1..c7de9ad34 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "52a680a0eb446dcaa74bc35e158aca57",
+ "content-hash": "a78cf8fdfec25eac43de77c05640dc91",
"packages": [
{
"name": "amphp/amp",
@@ -870,16 +870,16 @@
},
{
"name": "aws/aws-sdk-php",
- "version": "3.351.1",
+ "version": "3.352.0",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805"
+ "reference": "7f3ad0da2545b24259273ea7ab892188bae7d91b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805",
- "reference": "f3e20c8cdd2cc5827d77a0b3c0872fab89cdf805",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7f3ad0da2545b24259273ea7ab892188bae7d91b",
+ "reference": "7f3ad0da2545b24259273ea7ab892188bae7d91b",
"shasum": ""
},
"require": {
@@ -961,9 +961,9 @@
"support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.351.1"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.352.0"
},
- "time": "2025-07-17T18:07:08+00:00"
+ "time": "2025-08-01T18:04:23+00:00"
},
{
"name": "bacon/bacon-qr-code",
@@ -1373,16 +1373,16 @@
},
{
"name": "doctrine/dbal",
- "version": "4.3.0",
+ "version": "4.3.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
- "reference": "5fe09532be619202d59c70956c6fb20e97933ee3"
+ "reference": "ac336c95ea9e13433d56ca81c308b39db0e1a2a7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/dbal/zipball/5fe09532be619202d59c70956c6fb20e97933ee3",
- "reference": "5fe09532be619202d59c70956c6fb20e97933ee3",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/ac336c95ea9e13433d56ca81c308b39db0e1a2a7",
+ "reference": "ac336c95ea9e13433d56ca81c308b39db0e1a2a7",
"shasum": ""
},
"require": {
@@ -1459,7 +1459,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
- "source": "https://github.com/doctrine/dbal/tree/4.3.0"
+ "source": "https://github.com/doctrine/dbal/tree/4.3.1"
},
"funding": [
{
@@ -1475,7 +1475,7 @@
"type": "tidelift"
}
],
- "time": "2025-06-16T19:31:04+00:00"
+ "time": "2025-07-22T10:09:51+00:00"
},
{
"name": "doctrine/deprecations",
@@ -2678,16 +2678,16 @@
},
{
"name": "laravel/framework",
- "version": "v12.20.0",
+ "version": "v12.21.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff"
+ "reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/1b9a00f8caf5503c92aa436279172beae1a484ff",
- "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
+ "reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
"shasum": ""
},
"require": {
@@ -2889,7 +2889,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2025-07-08T15:02:21+00:00"
+ "time": "2025-07-22T15:41:55+00:00"
},
{
"name": "laravel/horizon",
@@ -3111,16 +3111,16 @@
},
{
"name": "laravel/sanctum",
- "version": "v4.1.2",
+ "version": "v4.2.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
- "reference": "e4c09e69aecd5a383e0c1b85a6bb501c997d7491"
+ "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/sanctum/zipball/e4c09e69aecd5a383e0c1b85a6bb501c997d7491",
- "reference": "e4c09e69aecd5a383e0c1b85a6bb501c997d7491",
+ "url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
+ "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
"shasum": ""
},
"require": {
@@ -3171,7 +3171,7 @@
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
- "time": "2025-07-01T15:49:32+00:00"
+ "time": "2025-07-09T19:45:24+00:00"
},
{
"name": "laravel/serializable-closure",
@@ -3236,16 +3236,16 @@
},
{
"name": "laravel/socialite",
- "version": "v5.21.0",
+ "version": "v5.23.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
- "reference": "d83639499ad14985c9a6a9713b70073300ce998d"
+ "reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/socialite/zipball/d83639499ad14985c9a6a9713b70073300ce998d",
- "reference": "d83639499ad14985c9a6a9713b70073300ce998d",
+ "url": "https://api.github.com/repos/laravel/socialite/zipball/e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5",
+ "reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5",
"shasum": ""
},
"require": {
@@ -3304,7 +3304,7 @@
"issues": "https://github.com/laravel/socialite/issues",
"source": "https://github.com/laravel/socialite"
},
- "time": "2025-05-19T12:56:37+00:00"
+ "time": "2025-07-23T14:16:08+00:00"
},
{
"name": "laravel/tinker",
@@ -3510,16 +3510,16 @@
},
{
"name": "league/commonmark",
- "version": "2.7.0",
+ "version": "2.7.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
- "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405"
+ "reference": "10732241927d3971d28e7ea7b5712721fa2296ca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
- "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca",
+ "reference": "10732241927d3971d28e7ea7b5712721fa2296ca",
"shasum": ""
},
"require": {
@@ -3548,7 +3548,7 @@
"symfony/process": "^5.4 | ^6.0 | ^7.0",
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0",
"unleashedtech/php-coding-standard": "^3.1.1",
- "vimeo/psalm": "^4.24.0 || ^5.0.0"
+ "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0"
},
"suggest": {
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
@@ -3613,7 +3613,7 @@
"type": "tidelift"
}
],
- "time": "2025-05-05T12:20:28+00:00"
+ "time": "2025-07-20T12:47:49+00:00"
},
{
"name": "league/config",
@@ -4696,16 +4696,16 @@
},
{
"name": "nesbot/carbon",
- "version": "3.10.1",
+ "version": "3.10.2",
"source": {
"type": "git",
"url": "https://github.com/CarbonPHP/carbon.git",
- "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00"
+ "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/1fd1935b2d90aef2f093c5e35f7ae1257c448d00",
- "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24",
+ "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24",
"shasum": ""
},
"require": {
@@ -4797,7 +4797,7 @@
"type": "tidelift"
}
],
- "time": "2025-06-21T15:19:35+00:00"
+ "time": "2025-08-02T09:36:06+00:00"
},
{
"name": "nette/schema",
@@ -4949,16 +4949,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.5.0",
+ "version": "v5.6.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
+ "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
- "reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56",
+ "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56",
"shasum": ""
},
"require": {
@@ -5001,9 +5001,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0"
},
- "time": "2025-05-31T08:24:38+00:00"
+ "time": "2025-07-27T20:03:57+00:00"
},
{
"name": "nubs/random-name-generator",
@@ -6663,16 +6663,16 @@
},
{
"name": "psy/psysh",
- "version": "v0.12.9",
+ "version": "v0.12.10",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
- "reference": "1b801844becfe648985372cb4b12ad6840245ace"
+ "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/bobthecow/psysh/zipball/1b801844becfe648985372cb4b12ad6840245ace",
- "reference": "1b801844becfe648985372cb4b12ad6840245ace",
+ "url": "https://api.github.com/repos/bobthecow/psysh/zipball/6e80abe6f2257121f1eb9a4c55bf29d921025b22",
+ "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22",
"shasum": ""
},
"require": {
@@ -6722,12 +6722,11 @@
"authors": [
{
"name": "Justin Hileman",
- "email": "justin@justinhileman.info",
- "homepage": "http://justinhileman.com"
+ "email": "justin@justinhileman.info"
}
],
"description": "An interactive shell for modern PHP.",
- "homepage": "http://psysh.org",
+ "homepage": "https://psysh.org",
"keywords": [
"REPL",
"console",
@@ -6736,9 +6735,9 @@
],
"support": {
"issues": "https://github.com/bobthecow/psysh/issues",
- "source": "https://github.com/bobthecow/psysh/tree/v0.12.9"
+ "source": "https://github.com/bobthecow/psysh/tree/v0.12.10"
},
- "time": "2025-06-23T02:35:06+00:00"
+ "time": "2025-08-04T12:39:37+00:00"
},
{
"name": "purplepixie/phpdns",
@@ -7247,16 +7246,16 @@
},
{
"name": "sentry/sentry",
- "version": "4.14.1",
+ "version": "4.14.2",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-php.git",
- "reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda"
+ "reference": "bfeec74303d60d3f8bc33701ab3e86f8a8729f17"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/a28c4a6f5fda2bf730789a638501d7a737a64eda",
- "reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda",
+ "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/bfeec74303d60d3f8bc33701ab3e86f8a8729f17",
+ "reference": "bfeec74303d60d3f8bc33701ab3e86f8a8729f17",
"shasum": ""
},
"require": {
@@ -7320,7 +7319,7 @@
],
"support": {
"issues": "https://github.com/getsentry/sentry-php/issues",
- "source": "https://github.com/getsentry/sentry-php/tree/4.14.1"
+ "source": "https://github.com/getsentry/sentry-php/tree/4.14.2"
},
"funding": [
{
@@ -7332,7 +7331,7 @@
"type": "custom"
}
],
- "time": "2025-06-23T15:25:52+00:00"
+ "time": "2025-07-21T08:28:29+00:00"
},
{
"name": "sentry/sentry-laravel",
@@ -7903,6 +7902,66 @@
],
"time": "2025-05-08T15:41:09+00:00"
},
+ {
+ "name": "spatie/commonmark-shiki-highlighter",
+ "version": "2.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/commonmark-shiki-highlighter.git",
+ "reference": "595c7e0b45d4a63b17dfc1ccbd13532d431ec351"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/commonmark-shiki-highlighter/zipball/595c7e0b45d4a63b17dfc1ccbd13532d431ec351",
+ "reference": "595c7e0b45d4a63b17dfc1ccbd13532d431ec351",
+ "shasum": ""
+ },
+ "require": {
+ "league/commonmark": "^2.4.2",
+ "php": "^8.0",
+ "spatie/shiki-php": "^2.2.2",
+ "symfony/process": "^5.4|^6.4|^7.1"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.19|^v3.49.0",
+ "phpunit/phpunit": "^9.5",
+ "spatie/phpunit-snapshot-assertions": "^4.2.7",
+ "spatie/ray": "^1.28"
+ },
+ "type": "commonmark-extension",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\CommonMarkShikiHighlighter\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Highlight code blocks with league/commonmark and Shiki",
+ "homepage": "https://github.com/spatie/commonmark-shiki-highlighter",
+ "keywords": [
+ "commonmark-shiki-highlighter",
+ "spatie"
+ ],
+ "support": {
+ "source": "https://github.com/spatie/commonmark-shiki-highlighter/tree/2.5.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2025-01-13T11:25:47+00:00"
+ },
{
"name": "spatie/laravel-activitylog",
"version": "4.10.2",
@@ -8077,6 +8136,82 @@
],
"time": "2025-06-25T11:36:37+00:00"
},
+ {
+ "name": "spatie/laravel-markdown",
+ "version": "2.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-markdown.git",
+ "reference": "353e7f9fae62826e26cbadef58a12ecf39685280"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-markdown/zipball/353e7f9fae62826e26cbadef58a12ecf39685280",
+ "reference": "353e7f9fae62826e26cbadef58a12ecf39685280",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/cache": "^9.0|^10.0|^11.0|^12.0",
+ "illuminate/contracts": "^9.0|^10.0|^11.0|^12.0",
+ "illuminate/support": "^9.0|^10.0|^11.0|^12.0",
+ "illuminate/view": "^9.0|^10.0|^11.0|^12.0",
+ "league/commonmark": "^2.6.0",
+ "php": "^8.1",
+ "spatie/commonmark-shiki-highlighter": "^2.5",
+ "spatie/laravel-package-tools": "^1.4.3"
+ },
+ "require-dev": {
+ "brianium/paratest": "^6.2|^7.8",
+ "nunomaduro/collision": "^5.3|^6.0|^7.0|^8.0",
+ "orchestra/testbench": "^6.15|^7.0|^8.0|^10.0",
+ "pestphp/pest": "^1.22|^2.0|^3.7",
+ "phpunit/phpunit": "^9.3|^11.5.3",
+ "spatie/laravel-ray": "^1.23",
+ "spatie/pest-plugin-snapshots": "^1.1|^2.2|^3.0",
+ "vimeo/psalm": "^4.8|^6.7"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Spatie\\LaravelMarkdown\\MarkdownServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Spatie\\LaravelMarkdown\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "A highly configurable markdown renderer and Blade component for Laravel",
+ "homepage": "https://github.com/spatie/laravel-markdown",
+ "keywords": [
+ "Laravel-Markdown",
+ "laravel",
+ "spatie"
+ ],
+ "support": {
+ "source": "https://github.com/spatie/laravel-markdown/tree/2.7.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-21T13:43:18+00:00"
+ },
{
"name": "spatie/laravel-package-tools",
"version": "1.92.7",
@@ -8516,6 +8651,71 @@
],
"time": "2025-04-18T08:17:40+00:00"
},
+ {
+ "name": "spatie/shiki-php",
+ "version": "2.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/shiki-php.git",
+ "reference": "a2e78a9ff8a1290b25d550be8fbf8285c13175c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/shiki-php/zipball/a2e78a9ff8a1290b25d550be8fbf8285c13175c5",
+ "reference": "a2e78a9ff8a1290b25d550be8fbf8285c13175c5",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^8.0",
+ "symfony/process": "^5.4|^6.4|^7.1"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^v3.0",
+ "pestphp/pest": "^1.8",
+ "phpunit/phpunit": "^9.5",
+ "spatie/pest-plugin-snapshots": "^1.1",
+ "spatie/ray": "^1.10"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\ShikiPhp\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Rias Van der Veken",
+ "email": "rias@spatie.be",
+ "role": "Developer"
+ },
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Highlight code using Shiki in PHP",
+ "homepage": "https://github.com/spatie/shiki-php",
+ "keywords": [
+ "shiki",
+ "spatie"
+ ],
+ "support": {
+ "source": "https://github.com/spatie/shiki-php/tree/2.3.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-21T14:16:57+00:00"
+ },
{
"name": "spatie/url",
"version": "2.4.0",
@@ -8779,16 +8979,16 @@
},
{
"name": "symfony/console",
- "version": "v7.3.1",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101"
+ "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101",
- "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101",
+ "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1",
+ "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1",
"shasum": ""
},
"require": {
@@ -8853,7 +9053,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.3.1"
+ "source": "https://github.com/symfony/console/tree/v7.3.2"
},
"funding": [
{
@@ -8864,12 +9064,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-27T19:55:54+00:00"
+ "time": "2025-07-30T17:13:41+00:00"
},
{
"name": "symfony/css-selector",
@@ -9005,16 +9209,16 @@
},
{
"name": "symfony/error-handler",
- "version": "v7.3.1",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
- "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235"
+ "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/error-handler/zipball/35b55b166f6752d6aaf21aa042fc5ed280fce235",
- "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3",
+ "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3",
"shasum": ""
},
"require": {
@@ -9062,7 +9266,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/error-handler/tree/v7.3.1"
+ "source": "https://github.com/symfony/error-handler/tree/v7.3.2"
},
"funding": [
{
@@ -9073,12 +9277,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-13T07:48:40+00:00"
+ "time": "2025-07-07T08:17:57+00:00"
},
{
"name": "symfony/event-dispatcher",
@@ -9238,16 +9446,16 @@
},
{
"name": "symfony/finder",
- "version": "v7.3.0",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d"
+ "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d",
- "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe",
+ "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe",
"shasum": ""
},
"require": {
@@ -9282,7 +9490,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v7.3.0"
+ "source": "https://github.com/symfony/finder/tree/v7.3.2"
},
"funding": [
{
@@ -9293,25 +9501,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-12-30T19:00:26+00:00"
+ "time": "2025-07-15T13:41:35+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v7.3.1",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "23dd60256610c86a3414575b70c596e5deff6ed9"
+ "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/23dd60256610c86a3414575b70c596e5deff6ed9",
- "reference": "23dd60256610c86a3414575b70c596e5deff6ed9",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6877c122b3a6cc3695849622720054f6e6fa5fa6",
+ "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6",
"shasum": ""
},
"require": {
@@ -9361,7 +9573,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v7.3.1"
+ "source": "https://github.com/symfony/http-foundation/tree/v7.3.2"
},
"funding": [
{
@@ -9372,25 +9584,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-23T15:07:14+00:00"
+ "time": "2025-07-10T08:47:49+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v7.3.1",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831"
+ "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1644879a66e4aa29c36fe33dfa6c54b450ce1831",
- "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6ecc895559ec0097e221ed2fd5eb44d5fede083c",
+ "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c",
"shasum": ""
},
"require": {
@@ -9475,7 +9691,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v7.3.1"
+ "source": "https://github.com/symfony/http-kernel/tree/v7.3.2"
},
"funding": [
{
@@ -9486,25 +9702,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-28T08:24:55+00:00"
+ "time": "2025-07-31T10:45:04+00:00"
},
{
"name": "symfony/mailer",
- "version": "v7.3.1",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/mailer.git",
- "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368"
+ "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mailer/zipball/b5db5105b290bdbea5ab27b89c69effcf1cb3368",
- "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368",
+ "url": "https://api.github.com/repos/symfony/mailer/zipball/d43e84d9522345f96ad6283d5dfccc8c1cfc299b",
+ "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b",
"shasum": ""
},
"require": {
@@ -9555,7 +9775,7 @@
"description": "Helps sending emails",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/mailer/tree/v7.3.1"
+ "source": "https://github.com/symfony/mailer/tree/v7.3.2"
},
"funding": [
{
@@ -9566,25 +9786,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-27T19:55:54+00:00"
+ "time": "2025-07-15T11:36:08+00:00"
},
{
"name": "symfony/mime",
- "version": "v7.3.0",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
- "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9"
+ "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mime/zipball/0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9",
- "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1",
+ "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1",
"shasum": ""
},
"require": {
@@ -9639,7 +9863,7 @@
"mime-type"
],
"support": {
- "source": "https://github.com/symfony/mime/tree/v7.3.0"
+ "source": "https://github.com/symfony/mime/tree/v7.3.2"
},
"funding": [
{
@@ -9650,25 +9874,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-02-19T08:51:26+00:00"
+ "time": "2025-07-15T13:41:35+00:00"
},
{
"name": "symfony/options-resolver",
- "version": "v7.3.0",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
- "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca"
+ "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca",
- "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca",
+ "url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37",
+ "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37",
"shasum": ""
},
"require": {
@@ -9706,7 +9934,7 @@
"options"
],
"support": {
- "source": "https://github.com/symfony/options-resolver/tree/v7.3.0"
+ "source": "https://github.com/symfony/options-resolver/tree/v7.3.2"
},
"funding": [
{
@@ -9717,12 +9945,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-04-04T13:12:05+00:00"
+ "time": "2025-07-15T11:36:08+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -10587,16 +10819,16 @@
},
{
"name": "symfony/routing",
- "version": "v7.3.0",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "8e213820c5fea844ecea29203d2a308019007c15"
+ "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/8e213820c5fea844ecea29203d2a308019007c15",
- "reference": "8e213820c5fea844ecea29203d2a308019007c15",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4",
+ "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4",
"shasum": ""
},
"require": {
@@ -10648,7 +10880,7 @@
"url"
],
"support": {
- "source": "https://github.com/symfony/routing/tree/v7.3.0"
+ "source": "https://github.com/symfony/routing/tree/v7.3.2"
},
"funding": [
{
@@ -10659,12 +10891,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-05-24T20:43:28+00:00"
+ "time": "2025-07-15T11:36:08+00:00"
},
{
"name": "symfony/service-contracts",
@@ -10813,16 +11049,16 @@
},
{
"name": "symfony/string",
- "version": "v7.3.0",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125"
+ "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125",
- "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125",
+ "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca",
+ "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca",
"shasum": ""
},
"require": {
@@ -10880,7 +11116,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.3.0"
+ "source": "https://github.com/symfony/string/tree/v7.3.2"
},
"funding": [
{
@@ -10891,25 +11127,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-04-20T20:19:01+00:00"
+ "time": "2025-07-10T08:47:49+00:00"
},
{
"name": "symfony/translation",
- "version": "v7.3.1",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "241d5ac4910d256660238a7ecf250deba4c73063"
+ "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/241d5ac4910d256660238a7ecf250deba4c73063",
- "reference": "241d5ac4910d256660238a7ecf250deba4c73063",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/81b48f4daa96272efcce9c7a6c4b58e629df3c90",
+ "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90",
"shasum": ""
},
"require": {
@@ -10976,7 +11216,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v7.3.1"
+ "source": "https://github.com/symfony/translation/tree/v7.3.2"
},
"funding": [
{
@@ -10987,12 +11227,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-27T19:55:54+00:00"
+ "time": "2025-07-30T17:31:46+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -11148,16 +11392,16 @@
},
{
"name": "symfony/var-dumper",
- "version": "v7.3.1",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42"
+ "reference": "53205bea27450dc5c65377518b3275e126d45e75"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e209fbe5f5a7b6043baba46fe5735a4b85d0d42",
- "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/53205bea27450dc5c65377518b3275e126d45e75",
+ "reference": "53205bea27450dc5c65377518b3275e126d45e75",
"shasum": ""
},
"require": {
@@ -11169,7 +11413,6 @@
"symfony/console": "<6.4"
},
"require-dev": {
- "ext-iconv": "*",
"symfony/console": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/process": "^6.4|^7.0",
@@ -11212,7 +11455,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v7.3.1"
+ "source": "https://github.com/symfony/var-dumper/tree/v7.3.2"
},
"funding": [
{
@@ -11223,25 +11466,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-27T19:55:54+00:00"
+ "time": "2025-07-29T20:02:46+00:00"
},
{
"name": "symfony/yaml",
- "version": "v7.3.1",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb"
+ "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb",
- "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/b8d7d868da9eb0919e99c8830431ea087d6aae30",
+ "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30",
"shasum": ""
},
"require": {
@@ -11284,7 +11531,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v7.3.1"
+ "source": "https://github.com/symfony/yaml/tree/v7.3.2"
},
"funding": [
{
@@ -11295,12 +11542,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-03T06:57:57+00:00"
+ "time": "2025-07-10T08:47:49+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@@ -12039,16 +12290,16 @@
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
- "version": "v3.15.4",
+ "version": "v3.16.0",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
- "reference": "c0667ea91f7185f1e074402c5788195e96bf8106"
+ "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0667ea91f7185f1e074402c5788195e96bf8106",
- "reference": "c0667ea91f7185f1e074402c5788195e96bf8106",
+ "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f265cf5e38577d42311f1a90d619bcd3740bea23",
+ "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23",
"shasum": ""
},
"require": {
@@ -12056,7 +12307,7 @@
"illuminate/session": "^9|^10|^11|^12",
"illuminate/support": "^9|^10|^11|^12",
"php": "^8.1",
- "php-debugbar/php-debugbar": "~2.1.1",
+ "php-debugbar/php-debugbar": "~2.2.0",
"symfony/finder": "^6|^7"
},
"require-dev": {
@@ -12076,7 +12327,7 @@
]
},
"branch-alias": {
- "dev-master": "3.15-dev"
+ "dev-master": "3.16-dev"
}
},
"autoload": {
@@ -12108,7 +12359,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
- "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.4"
+ "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.16.0"
},
"funding": [
{
@@ -12120,7 +12371,7 @@
"type": "github"
}
],
- "time": "2025-04-16T06:32:06+00:00"
+ "time": "2025-07-14T11:56:43+00:00"
},
{
"name": "brianium/paratest",
@@ -12641,16 +12892,16 @@
},
{
"name": "laravel/telescope",
- "version": "v5.10.0",
+ "version": "v5.10.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/telescope.git",
- "reference": "fc0a8662682c0375b534033873debb780c003486"
+ "reference": "6d249d93ab06dc147ac62ea02b4272c2e7a24b72"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/telescope/zipball/fc0a8662682c0375b534033873debb780c003486",
- "reference": "fc0a8662682c0375b534033873debb780c003486",
+ "url": "https://api.github.com/repos/laravel/telescope/zipball/6d249d93ab06dc147ac62ea02b4272c2e7a24b72",
+ "reference": "6d249d93ab06dc147ac62ea02b4272c2e7a24b72",
"shasum": ""
},
"require": {
@@ -12704,9 +12955,9 @@
],
"support": {
"issues": "https://github.com/laravel/telescope/issues",
- "source": "https://github.com/laravel/telescope/tree/v5.10.0"
+ "source": "https://github.com/laravel/telescope/tree/v5.10.2"
},
- "time": "2025-07-07T14:47:19+00:00"
+ "time": "2025-07-24T05:26:13+00:00"
},
{
"name": "mockery/mockery",
@@ -12793,16 +13044,16 @@
},
{
"name": "myclabs/deep-copy",
- "version": "1.13.3",
+ "version": "1.13.4",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
- "reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+ "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
"shasum": ""
},
"require": {
@@ -12841,7 +13092,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
},
"funding": [
{
@@ -12849,7 +13100,7 @@
"type": "tidelift"
}
],
- "time": "2025-07-05T12:25:42+00:00"
+ "time": "2025-08-01T08:46:24+00:00"
},
{
"name": "nunomaduro/collision",
@@ -13394,16 +13645,16 @@
},
{
"name": "php-debugbar/php-debugbar",
- "version": "v2.1.6",
+ "version": "v2.2.4",
"source": {
"type": "git",
"url": "https://github.com/php-debugbar/php-debugbar.git",
- "reference": "16fa68da5617220594aa5e33fa9de415f94784a0"
+ "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/16fa68da5617220594aa5e33fa9de415f94784a0",
- "reference": "16fa68da5617220594aa5e33fa9de415f94784a0",
+ "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/3146d04671f51f69ffec2a4207ac3bdcf13a9f35",
+ "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35",
"shasum": ""
},
"require": {
@@ -13411,6 +13662,9 @@
"psr/log": "^1|^2|^3",
"symfony/var-dumper": "^4|^5|^6|^7"
},
+ "replace": {
+ "maximebf/debugbar": "self.version"
+ },
"require-dev": {
"dbrekelmans/bdi": "^1",
"phpunit/phpunit": "^8|^9",
@@ -13425,7 +13679,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0-dev"
+ "dev-master": "2.1-dev"
}
},
"autoload": {
@@ -13458,9 +13712,9 @@
],
"support": {
"issues": "https://github.com/php-debugbar/php-debugbar/issues",
- "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.1.6"
+ "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.4"
},
- "time": "2025-02-21T17:47:03+00:00"
+ "time": "2025-07-22T14:01:30+00:00"
},
{
"name": "php-webdriver/webdriver",
@@ -13530,16 +13784,16 @@
},
{
"name": "phpstan/phpstan",
- "version": "2.1.18",
+ "version": "2.1.21",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "ee1f390b7a70cdf74a2b737e554f68afea885db7"
+ "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ee1f390b7a70cdf74a2b737e554f68afea885db7",
- "reference": "ee1f390b7a70cdf74a2b737e554f68afea885db7",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ccf445757458c06a04eb3f803603cb118fe5fa6",
+ "reference": "1ccf445757458c06a04eb3f803603cb118fe5fa6",
"shasum": ""
},
"require": {
@@ -13584,7 +13838,7 @@
"type": "github"
}
],
- "time": "2025-07-17T17:22:31+00:00"
+ "time": "2025-07-28T19:35:08+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -15436,16 +15690,16 @@
},
{
"name": "symfony/http-client",
- "version": "v7.3.1",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
- "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64"
+ "reference": "1c064a0c67749923483216b081066642751cc2c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/4403d87a2c16f33345dca93407a8714ee8c05a64",
- "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/1c064a0c67749923483216b081066642751cc2c7",
+ "reference": "1c064a0c67749923483216b081066642751cc2c7",
"shasum": ""
},
"require": {
@@ -15511,7 +15765,7 @@
"http"
],
"support": {
- "source": "https://github.com/symfony/http-client/tree/v7.3.1"
+ "source": "https://github.com/symfony/http-client/tree/v7.3.2"
},
"funding": [
{
@@ -15522,12 +15776,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-28T07:58:39+00:00"
+ "time": "2025-07-15T11:36:08+00:00"
},
{
"name": "symfony/http-client-contracts",
diff --git a/config/constants.php b/config/constants.php
index ae984aa18..bbd442654 100644
--- a/config/constants.php
+++ b/config/constants.php
@@ -2,9 +2,9 @@
return [
'coolify' => [
- 'version' => '4.0.0-beta.420.6',
- 'helper_version' => '1.0.8',
- 'realtime_version' => '1.0.9',
+ 'version' => '4.0.0-beta.420.7',
+ 'helper_version' => '1.0.9',
+ 'realtime_version' => '1.0.10',
'self_hosted' => env('SELF_HOSTED', true),
'autoupdate' => env('AUTOUPDATE'),
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
@@ -22,7 +22,8 @@ return [
'services' => [
// Temporary disabled until cache is implemented
// 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
- 'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json',
+ 'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/v4.x/templates/service-templates-latest.json',
+ 'file_name' => 'service-templates-latest.json',
],
'terminal' => [
@@ -69,6 +70,10 @@ return [
],
],
+ 'email_change' => [
+ 'verification_code_expiry_minutes' => 10,
+ ],
+
'sentry' => [
'sentry_dsn' => env('SENTRY_DSN'),
],
diff --git a/config/horizon.php b/config/horizon.php
index 6086b30da..cdabcb1e8 100644
--- a/config/horizon.php
+++ b/config/horizon.php
@@ -182,14 +182,15 @@ return [
'defaults' => [
's6' => [
'connection' => 'redis',
- 'queue' => ['high', 'default'],
- 'balance' => env('HORIZON_BALANCE', 'auto'),
- 'maxTime' => 0,
- 'maxJobs' => 0,
+ 'balance' => env('HORIZON_BALANCE', 'false'),
+ 'queue' => env('HORIZON_QUEUES', 'high,default'),
+ 'maxTime' => 3600,
+ 'maxJobs' => 400,
'memory' => 128,
'tries' => 1,
- 'timeout' => 3560,
'nice' => 0,
+ 'sleep' => 3,
+ 'timeout' => 3600,
],
],
@@ -198,7 +199,7 @@ return [
's6' => [
'autoScalingStrategy' => 'size',
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
- 'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
+ 'maxProcesses' => env('HORIZON_MAX_PROCESSES', 4),
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
],
@@ -208,7 +209,7 @@ return [
's6' => [
'autoScalingStrategy' => 'size',
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
- 'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
+ 'maxProcesses' => env('HORIZON_MAX_PROCESSES', 4),
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
],
diff --git a/config/logging.php b/config/logging.php
index 4c3df4ce1..488327414 100644
--- a/config/logging.php
+++ b/config/logging.php
@@ -118,6 +118,20 @@ return [
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
+
+ 'scheduled' => [
+ 'driver' => 'daily',
+ 'path' => storage_path('logs/scheduled.log'),
+ 'level' => 'debug',
+ 'days' => 1,
+ ],
+
+ 'scheduled-errors' => [
+ 'driver' => 'daily',
+ 'path' => storage_path('logs/scheduled-errors.log'),
+ 'level' => 'debug',
+ 'days' => 7,
+ ],
],
];
diff --git a/config/services.php b/config/services.php
index 7add50a5c..6a21cda18 100644
--- a/config/services.php
+++ b/config/services.php
@@ -65,6 +65,6 @@ return [
'client_secret' => env('ZITADEL_CLIENT_SECRET'),
'redirect' => env('ZITADEL_REDIRECT_URI'),
'base_url' => env('ZITADEL_BASE_URL'),
- ]
+ ],
];
diff --git a/database/migrations/2025_07_14_191016_add_deleted_at_to_application_previews_table.php b/database/migrations/2025_07_14_191016_add_deleted_at_to_application_previews_table.php
new file mode 100644
index 000000000..25aa0f5f0
--- /dev/null
+++ b/database/migrations/2025_07_14_191016_add_deleted_at_to_application_previews_table.php
@@ -0,0 +1,28 @@
+softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('application_previews', function (Blueprint $table) {
+ $table->dropSoftDeletes();
+ });
+ }
+};
diff --git a/database/migrations/2025_07_16_202201_add_timeout_to_scheduled_database_backups_table.php b/database/migrations/2025_07_16_202201_add_timeout_to_scheduled_database_backups_table.php
new file mode 100644
index 000000000..f8f8cb8ad
--- /dev/null
+++ b/database/migrations/2025_07_16_202201_add_timeout_to_scheduled_database_backups_table.php
@@ -0,0 +1,18 @@
+integer('timeout')->default(3600);
+ });
+ }
+};
diff --git a/database/migrations/2025_08_07_142403_create_user_changelog_reads_table.php b/database/migrations/2025_08_07_142403_create_user_changelog_reads_table.php
new file mode 100644
index 000000000..db8a42fb7
--- /dev/null
+++ b/database/migrations/2025_08_07_142403_create_user_changelog_reads_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->foreignId('user_id')->constrained()->onDelete('cascade');
+ $table->string('release_tag'); // GitHub tag_name (e.g., "v4.0.0-beta.420.6")
+ $table->timestamp('read_at');
+ $table->timestamps();
+
+ $table->unique(['user_id', 'release_tag']);
+ $table->index('user_id');
+ $table->index('release_tag');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('user_changelog_reads');
+ }
+};
diff --git a/database/migrations/2025_08_17_102422_add_disable_local_backup_to_scheduled_database_backups_table.php b/database/migrations/2025_08_17_102422_add_disable_local_backup_to_scheduled_database_backups_table.php
new file mode 100644
index 000000000..e414472df
--- /dev/null
+++ b/database/migrations/2025_08_17_102422_add_disable_local_backup_to_scheduled_database_backups_table.php
@@ -0,0 +1,28 @@
+boolean('disable_local_backup')->default(false)->after('save_s3');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('scheduled_database_backups', function (Blueprint $table) {
+ $table->dropColumn('disable_local_backup');
+ });
+ }
+};
diff --git a/database/migrations/2025_08_18_104146_add_email_change_fields_to_users_table.php b/database/migrations/2025_08_18_104146_add_email_change_fields_to_users_table.php
new file mode 100644
index 000000000..9cefe2c09
--- /dev/null
+++ b/database/migrations/2025_08_18_104146_add_email_change_fields_to_users_table.php
@@ -0,0 +1,30 @@
+string('pending_email')->nullable()->after('email');
+ $table->string('email_change_code', 6)->nullable()->after('pending_email');
+ $table->timestamp('email_change_code_expires_at')->nullable()->after('email_change_code');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->dropColumn(['pending_email', 'email_change_code', 'email_change_code_expires_at']);
+ });
+ }
+};
diff --git a/database/migrations/2025_08_18_154244_change_env_sorting_default_to_false.php b/database/migrations/2025_08_18_154244_change_env_sorting_default_to_false.php
new file mode 100644
index 000000000..32ed075ba
--- /dev/null
+++ b/database/migrations/2025_08_18_154244_change_env_sorting_default_to_false.php
@@ -0,0 +1,18 @@
+boolean('is_env_sorting_enabled')->default(false)->change();
+ });
+ }
+};
diff --git a/database/migrations/2025_08_21_080234_add_git_shallow_clone_to_application_settings_table.php b/database/migrations/2025_08_21_080234_add_git_shallow_clone_to_application_settings_table.php
new file mode 100644
index 000000000..399c49c7f
--- /dev/null
+++ b/database/migrations/2025_08_21_080234_add_git_shallow_clone_to_application_settings_table.php
@@ -0,0 +1,28 @@
+boolean('is_git_shallow_clone_enabled')->default(true)->after('is_git_lfs_enabled');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('application_settings', function (Blueprint $table) {
+ $table->dropColumn('is_git_shallow_clone_enabled');
+ });
+ }
+};
diff --git a/docker/coolify-helper/Dockerfile b/docker/coolify-helper/Dockerfile
index b62469cef..8c7073519 100644
--- a/docker/coolify-helper/Dockerfile
+++ b/docker/coolify-helper/Dockerfile
@@ -4,15 +4,15 @@ ARG BASE_IMAGE=alpine:3.21
# https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=28.0.0
# https://github.com/docker/compose/releases
-ARG DOCKER_COMPOSE_VERSION=2.34.0
+ARG DOCKER_COMPOSE_VERSION=2.38.2
# https://github.com/docker/buildx/releases
-ARG DOCKER_BUILDX_VERSION=0.22.0
+ARG DOCKER_BUILDX_VERSION=0.25.0
# https://github.com/buildpacks/pack/releases
-ARG PACK_VERSION=0.37.0
+ARG PACK_VERSION=0.38.2
# https://github.com/railwayapp/nixpacks/releases
-ARG NIXPACKS_VERSION=1.34.1
+ARG NIXPACKS_VERSION=1.39.0
# https://github.com/minio/mc/releases
-ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z
+ARG MINIO_VERSION=RELEASE.2025-05-21T01-59-54Z
FROM minio/mc:${MINIO_VERSION} AS minio-client
diff --git a/docker/coolify-realtime/Dockerfile b/docker/coolify-realtime/Dockerfile
index 7a24200d6..18c2f9301 100644
--- a/docker/coolify-realtime/Dockerfile
+++ b/docker/coolify-realtime/Dockerfile
@@ -2,7 +2,7 @@
# https://github.com/soketi/soketi/releases
ARG SOKETI_VERSION=1.6-16-alpine
# https://github.com/cloudflare/cloudflared/releases
-ARG CLOUDFLARED_VERSION=2025.5.0
+ARG CLOUDFLARED_VERSION=2025.7.0
FROM quay.io/soketi/soketi:${SOKETI_VERSION}
diff --git a/docker/coolify-realtime/package-lock.json b/docker/coolify-realtime/package-lock.json
index 1c329e47f..49907cbd4 100644
--- a/docker/coolify-realtime/package-lock.json
+++ b/docker/coolify-realtime/package-lock.json
@@ -181,14 +181,15 @@
}
},
"node_modules/form-data": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
- "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -323,9 +324,9 @@
}
},
"node_modules/nan": {
- "version": "2.22.1",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.1.tgz",
- "integrity": "sha512-pfRR4ZcNTSm2ZFHaztuvbICf+hyiG6ecA06SfAxoPmuHjvMu0KUIae7Y8GyVkbBqeEIidsmXeYooWIX9+qjfRQ==",
+ "version": "2.23.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
+ "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==",
"license": "MIT"
},
"node_modules/node-pty": {
diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile
index 8c5beec07..85cce14d7 100644
--- a/docker/development/Dockerfile
+++ b/docker/development/Dockerfile
@@ -2,9 +2,9 @@
# https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine
ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine
# https://github.com/minio/mc/releases
-ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z
+ARG MINIO_VERSION=RELEASE.2025-05-21T01-59-54Z
# https://github.com/cloudflare/cloudflared/releases
-ARG CLOUDFLARED_VERSION=2025.2.0
+ARG CLOUDFLARED_VERSION=2025.7.0
# https://www.postgresql.org/support/versioning/
ARG POSTGRES_VERSION=15
diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile
index 5633170e3..6c9628a81 100644
--- a/docker/production/Dockerfile
+++ b/docker/production/Dockerfile
@@ -2,9 +2,9 @@
# https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine
ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine
# https://github.com/minio/mc/releases
-ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z
+ARG MINIO_VERSION=RELEASE.2025-05-21T01-59-54Z
# https://github.com/cloudflare/cloudflared/releases
-ARG CLOUDFLARED_VERSION=2025.2.0
+ARG CLOUDFLARED_VERSION=2025.7.0
# https://www.postgresql.org/support/versioning/
ARG POSTGRES_VERSION=15
@@ -120,6 +120,7 @@ COPY --chown=www-data:www-data templates ./templates
COPY --chown=www-data:www-data resources/views ./resources/views
COPY --chown=www-data:www-data artisan artisan
COPY --chown=www-data:www-data openapi.yaml ./openapi.yaml
+COPY --chown=www-data:www-data changelogs/ ./changelogs/
RUN composer dump-autoload
diff --git a/docker/testing-host/Dockerfile b/docker/testing-host/Dockerfile
index b19d0875c..fdad3cc41 100644
--- a/docker/testing-host/Dockerfile
+++ b/docker/testing-host/Dockerfile
@@ -2,9 +2,9 @@
# https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=28.0.0
# https://github.com/docker/compose/releases
-ARG DOCKER_COMPOSE_VERSION=2.34.0
+ARG DOCKER_COMPOSE_VERSION=2.38.2
# https://github.com/docker/buildx/releases
-ARG DOCKER_BUILDX_VERSION=0.22.0
+ARG DOCKER_BUILDX_VERSION=0.25.0
FROM debian:12-slim
diff --git a/lang/ar.json b/lang/ar.json
index 263924c24..c966cc686 100644
--- a/lang/ar.json
+++ b/lang/ar.json
@@ -11,7 +11,8 @@
"auth.login.infomaniak": "تسجيل الدخول باستخدام Infomaniak",
"auth.already_registered": "هل سبق لك التسجيل؟",
"auth.confirm_password": "تأكيد كلمة المرور",
- "auth.forgot_password": "نسيت كلمة المرور",
+ "auth.forgot_password_link": "هل نسيت كلمة المرور؟",
+ "auth.forgot_password_heading": "استعادة كلمة المرور",
"auth.forgot_password_send_email": "إرسال بريد إلكتروني لإعادة تعيين كلمة المرور",
"auth.register_now": "تسجيل",
"auth.logout": "تسجيل الخروج",
@@ -39,4 +40,4 @@
"resource.delete_configurations": "حذف جميع ملفات التعريف من الخادم بشكل دائم.",
"database.delete_backups_locally": "حذف كافة النسخ الاحتياطية نهائيًا من التخزين المحلي.",
"warning.sslipdomain": "تم حفظ ملفات التعريف الخاصة بك، ولكن استخدام نطاق sslip مع https غير مستحسن، لأن خوادم Let's Encrypt مع هذا النطاق العام محدودة المعدل (ستفشل عملية التحقق من شهادة SSL).
استخدم نطاقك الخاص بدلاً من ذلك."
-}
+}
\ No newline at end of file
diff --git a/lang/az.json b/lang/az.json
index 92f56ddbc..85cee7589 100644
--- a/lang/az.json
+++ b/lang/az.json
@@ -11,7 +11,8 @@
"auth.login.infomaniak": "Infomaniak ilə daxil ol",
"auth.already_registered": "Qeytiyatınız var?",
"auth.confirm_password": "Şifrəni təsdiqləyin",
- "auth.forgot_password": "Şifrəmi unutdum",
+ "auth.forgot_password_link": "Şifrəmi unutdum?",
+ "auth.forgot_password_heading": "Şifrəni bərpa et",
"auth.forgot_password_send_email": "Şifrəni sıfırlamaq üçün e-poçt göndər",
"auth.register_now": "Qeydiyyat",
"auth.logout": "Çıxış",
@@ -39,4 +40,4 @@
"resource.delete_configurations": "Serverdən bütün konfiqurasiya faylları tamamilə silinəcək.",
"database.delete_backups_locally": "Bütün ehtiyat nüsxələr lokal yaddaşdan tamamilə silinəcək.",
"warning.sslipdomain": "Konfiqurasiya yadda saxlanıldı, lakin sslip domeni ilə https TÖVSİYƏ EDİLMİR, çünki Let's Encrypt serverləri bu ümumi domenlə məhdudlaşdırılır (SSL sertifikatının təsdiqlənməsi uğursuz olacaq).
Əvəzində öz domeninizdən istifadə edin."
-}
+}
\ No newline at end of file
diff --git a/lang/cs.json b/lang/cs.json
index 00455aa81..9e5d2c44e 100644
--- a/lang/cs.json
+++ b/lang/cs.json
@@ -10,7 +10,8 @@
"auth.login.infomaniak": "Přihlásit se pomocí Infomaniak",
"auth.already_registered": "Již jste registrováni?",
"auth.confirm_password": "Potvrďte heslo",
- "auth.forgot_password": "Zapomněli jste heslo",
+ "auth.forgot_password_link": "Zapomněli jste heslo?",
+ "auth.forgot_password_heading": "Obnovení hesla",
"auth.forgot_password_send_email": "Poslat e-mail pro resetování hesla",
"auth.register_now": "Registrovat se",
"auth.logout": "Odhlásit se",
@@ -30,4 +31,4 @@
"input.recovery_code": "Obnovovací kód",
"button.save": "Uložit",
"repository.url": "Příklady
Pro veřejné repozitáře, použijte https://....
Pro soukromé repozitáře, použijte git@....
https://github.com/coollabsio/coolify-examples main branch bude zvolena
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch bude vybrána.
https://gitea.com/sedlav/expressjs.git main branch vybrána.
https://gitlab.com/andrasbacsai/nodejs-example.git main branch bude vybrána."
-}
+}
\ No newline at end of file
diff --git a/lang/de.json b/lang/de.json
index f56b21710..fd587de22 100644
--- a/lang/de.json
+++ b/lang/de.json
@@ -11,7 +11,8 @@
"auth.login.zitadel": "Mit Zitadel anmelden",
"auth.already_registered": "Bereits registriert?",
"auth.confirm_password": "Passwort bestätigen",
- "auth.forgot_password": "Passwort vergessen",
+ "auth.forgot_password_link": "Passwort vergessen?",
+ "auth.forgot_password_heading": "Passwort-Wiederherstellung",
"auth.forgot_password_send_email": "Passwort zurücksetzen E-Mail senden",
"auth.register_now": "Registrieren",
"auth.logout": "Abmelden",
@@ -31,4 +32,4 @@
"input.recovery_code": "Wiederherstellungscode",
"button.save": "Speichern",
"repository.url": "Beispiele
Für öffentliche Repositories benutze https://....
Für private Repositories benutze git@....
https://github.com/coollabsio/coolify-examples main Branch wird ausgewählt
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify Branch wird ausgewählt.
https://gitea.com/sedlav/expressjs.git main Branch wird ausgewählt.
https://gitlab.com/andrasbacsai/nodejs-example.git main Branch wird ausgewählt."
-}
+}
\ No newline at end of file
diff --git a/lang/en.json b/lang/en.json
index 4a398a9f9..af7f2145d 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -12,7 +12,8 @@
"auth.login.zitadel": "Login with Zitadel",
"auth.already_registered": "Already registered?",
"auth.confirm_password": "Confirm password",
- "auth.forgot_password": "Forgot password",
+ "auth.forgot_password_link": "Forgot password?",
+ "auth.forgot_password_heading": "Password recovery",
"auth.forgot_password_send_email": "Send password reset email",
"auth.register_now": "Register",
"auth.logout": "Logout",
@@ -40,4 +41,4 @@
"resource.delete_configurations": "Permanently delete all configuration files from the server.",
"database.delete_backups_locally": "All backups will be permanently deleted from local storage.",
"warning.sslipdomain": "Your configuration is saved, but sslip domain with https is NOT recommended, because Let's Encrypt servers with this public domain are rate limited (SSL certificate validation will fail).
Use your own domain instead."
-}
+}
\ No newline at end of file
diff --git a/lang/es.json b/lang/es.json
index 73363a9bf..f56387f05 100644
--- a/lang/es.json
+++ b/lang/es.json
@@ -10,7 +10,8 @@
"auth.login.infomaniak": "Acceder con Infomaniak",
"auth.already_registered": "¿Ya estás registrado?",
"auth.confirm_password": "Confirmar contraseña",
- "auth.forgot_password": "¿Olvidaste tu contraseña?",
+ "auth.forgot_password_link": "¿Olvidaste tu contraseña?",
+ "auth.forgot_password_heading": "Recuperación de contraseña",
"auth.forgot_password_send_email": "Enviar correo de recuperación de contraseña",
"auth.register_now": "Registrar",
"auth.logout": "Cerrar sesión",
@@ -30,4 +31,4 @@
"input.recovery_code": "Código de recuperación",
"button.save": "Guardar",
"repository.url": "Examples
Para repositorios públicos, usar https://....
Para repositorios privados, usar git@....
https://github.com/coollabsio/coolify-examples main la rama 'main' será seleccionada.
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify la rama 'nodejs-fastify' será seleccionada.
https://gitea.com/sedlav/expressjs.git main la rama 'main' será seleccionada.
https://gitlab.com/andrasbacsai/nodejs-example.git main la rama 'main' será seleccionada."
-}
+}
\ No newline at end of file
diff --git a/lang/fa.json b/lang/fa.json
index d68049e77..ae22ee946 100644
--- a/lang/fa.json
+++ b/lang/fa.json
@@ -10,7 +10,8 @@
"auth.login.infomaniak": "ورود با Infomaniak",
"auth.already_registered": "قبلاً ثبت نام کردهاید؟",
"auth.confirm_password": "تایید رمز عبور",
- "auth.forgot_password": "فراموشی رمز عبور",
+ "auth.forgot_password_link": "رمز عبور را فراموش کردهاید؟",
+ "auth.forgot_password_heading": "بازیابی رمز عبور",
"auth.forgot_password_send_email": "ارسال ایمیل بازیابی رمز عبور",
"auth.register_now": "ثبت نام",
"auth.logout": "خروج",
@@ -30,4 +31,4 @@
"input.recovery_code": "کد بازیابی",
"button.save": "ذخیره",
"repository.url": "مثالها
برای مخازن عمومی، از https://... استفاده کنید.
برای مخازن خصوصی، از git@... استفاده کنید.
شاخه main انتخاب خواهد شد.
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify شاخه nodejs-fastify انتخاب خواهد شد.
https://gitea.com/sedlav/expressjs.git شاخه main انتخاب خواهد شد.
https://gitlab.com/andrasbacsai/nodejs-example.git شاخه main انتخاب خواهد شد."
-}
+}
\ No newline at end of file
diff --git a/lang/fr.json b/lang/fr.json
index 2516d0f69..d98a1ebc8 100644
--- a/lang/fr.json
+++ b/lang/fr.json
@@ -11,7 +11,8 @@
"auth.login.infomaniak": "Connexion avec Infomaniak",
"auth.already_registered": "Déjà enregistré ?",
"auth.confirm_password": "Confirmer le mot de passe",
- "auth.forgot_password": "Mot de passe oublié",
+ "auth.forgot_password_link": "Mot de passe oublié ?",
+ "auth.forgot_password_heading": "Récupération du mot de passe",
"auth.forgot_password_send_email": "Envoyer l'email de réinitialisation de mot de passe",
"auth.register_now": "S'enregistrer",
"auth.logout": "Déconnexion",
@@ -39,4 +40,4 @@
"resource.delete_configurations": "Supprimer définitivement tous les fichiers de configuration du serveur.",
"database.delete_backups_locally": "Toutes les sauvegardes seront définitivement supprimées du stockage local.",
"warning.sslipdomain": "Votre configuration est enregistrée, mais l'utilisation du domaine sslip avec https N'EST PAS recommandée, car les serveurs Let's Encrypt avec ce domaine public sont limités en taux (la validation du certificat SSL échouera).
Utilisez plutôt votre propre domaine."
-}
+}
\ No newline at end of file
diff --git a/lang/id.json b/lang/id.json
index b0e38197a..d85176cda 100644
--- a/lang/id.json
+++ b/lang/id.json
@@ -11,7 +11,8 @@
"auth.login.infomaniak": "Masuk dengan Infomaniak",
"auth.already_registered": "Sudah terdaftar?",
"auth.confirm_password": "Konfirmasi kata sandi",
- "auth.forgot_password": "Lupa kata sandi",
+ "auth.forgot_password_link": "Lupa kata sandi?",
+ "auth.forgot_password_heading": "Pemulihan Kata Sandi",
"auth.forgot_password_send_email": "Kirim email reset kata sandi",
"auth.register_now": "Daftar",
"auth.logout": "Keluar",
@@ -39,4 +40,4 @@
"resource.delete_configurations": "Hapus permanen semua file konfigurasi dari server.",
"database.delete_backups_locally": "Semua backup akan dihapus permanen dari penyimpanan lokal.",
"warning.sslipdomain": "Konfigurasi Anda disimpan, tetapi domain sslip dengan https TIDAK direkomendasikan, karena server Let's Encrypt dengan domain publik ini dibatasi (validasi sertifikat SSL akan gagal).
Gunakan domain Anda sendiri sebagai gantinya."
-}
+}
\ No newline at end of file
diff --git a/lang/it.json b/lang/it.json
index c0edc314b..e4c1a9c05 100644
--- a/lang/it.json
+++ b/lang/it.json
@@ -11,7 +11,8 @@
"auth.login.infomaniak": "Accedi con Infomaniak",
"auth.already_registered": "Già registrato?",
"auth.confirm_password": "Conferma password",
- "auth.forgot_password": "Password dimenticata",
+ "auth.forgot_password_link": "Hai dimenticato la password?",
+ "auth.forgot_password_heading": "Recupero password",
"auth.forgot_password_send_email": "Invia email per reimpostare la password",
"auth.register_now": "Registrati",
"auth.logout": "Esci",
@@ -39,4 +40,4 @@
"resource.delete_configurations": "Elimina definitivamente tutti i file di configurazione dal server.",
"database.delete_backups_locally": "Tutti i backup verranno eliminati definitivamente dall'archiviazione locale.",
"warning.sslipdomain": "La tua configurazione è stata salvata, ma il dominio sslip con https NON è raccomandato, poiché i server di Let's Encrypt con questo dominio pubblico hanno limitazioni di frequenza (la convalida del certificato SSL fallirà).
Utilizza invece il tuo dominio personale."
-}
+}
\ No newline at end of file
diff --git a/lang/ja.json b/lang/ja.json
index 87d87d99b..05987e7ce 100644
--- a/lang/ja.json
+++ b/lang/ja.json
@@ -10,7 +10,8 @@
"auth.login.infomaniak": "Infomaniakでログイン",
"auth.already_registered": "すでに登録済みですか?",
"auth.confirm_password": "パスワードを確認",
- "auth.forgot_password": "パスワードを忘れた",
+ "auth.forgot_password_link": "パスワードをお忘れですか?",
+ "auth.forgot_password_heading": "パスワードの再設定",
"auth.forgot_password_send_email": "パスワードリセットメールを送信",
"auth.register_now": "今すぐ登録",
"auth.logout": "ログアウト",
@@ -30,4 +31,4 @@
"input.recovery_code": "リカバリーコード",
"button.save": "保存",
"repository.url": "例
公開リポジトリの場合はhttps://...を使用してください。
プライベートリポジトリの場合はgit@...を使用してください。
https://github.com/coollabsio/coolify-examples mainブランチが選択されます
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastifyブランチが選択されます。
https://gitea.com/sedlav/expressjs.git mainブランチが選択されます。
https://gitlab.com/andrasbacsai/nodejs-example.git mainブランチが選択されます。"
-}
+}
\ No newline at end of file
diff --git a/lang/no.json b/lang/no.json
index a84f6aa6c..967bdf606 100644
--- a/lang/no.json
+++ b/lang/no.json
@@ -11,7 +11,8 @@
"auth.login.infomaniak": "Logg inn med Infomaniak",
"auth.already_registered": "Allerede registrert?",
"auth.confirm_password": "Bekreft passord",
- "auth.forgot_password": "Glemt passord",
+ "auth.forgot_password_link": "Glemt passord?",
+ "auth.forgot_password_heading": "Gjenoppretting av passord",
"auth.forgot_password_send_email": "Send e-post for tilbakestilling av passord",
"auth.register_now": "Registrer deg",
"auth.logout": "Logg ut",
@@ -39,4 +40,4 @@
"resource.delete_configurations": "Slett alle konfigurasjonsfiler fra serveren permanent.",
"database.delete_backups_locally": "Alle sikkerhetskopier vil bli slettet permanent fra lokal lagring.",
"warning.sslipdomain": "Konfigurasjonen din er lagret, men sslip-domene med https er IKKE anbefalt, fordi Let's Encrypt-servere med dette offentlige domenet er hastighetsbegrenset (SSL-sertifikatvalidering vil mislykkes).
Bruk ditt eget domene i stedet."
-}
+}
\ No newline at end of file
diff --git a/lang/pl.json b/lang/pl.json
new file mode 100644
index 000000000..bcd8e2393
--- /dev/null
+++ b/lang/pl.json
@@ -0,0 +1,44 @@
+{
+ "auth.login": "Zaloguj",
+ "auth.login.authentik": "Zaloguj się przez Authentik",
+ "auth.login.azure": "Zaloguj się przez Microsoft",
+ "auth.login.bitbucket": "Zaloguj się przez Bitbucket",
+ "auth.login.clerk": "Zaloguj się przez Clerk",
+ "auth.login.discord": "Zaloguj się przez Discord",
+ "auth.login.github": "Zaloguj się przez GitHub",
+ "auth.login.gitlab": "Zaloguj się przez Gitlab",
+ "auth.login.google": "Zaloguj się przez Google",
+ "auth.login.infomaniak": "Zaloguj się przez Infomaniak",
+ "auth.login.zitadel": "Zaloguj się przez Zitadel",
+ "auth.already_registered": "Już zarejestrowany?",
+ "auth.confirm_password": "Potwierdź hasło",
+ "auth.forgot_password_link": "Zapomniałeś hasło?",
+ "auth.forgot_password_heading": "Odzyskiwanie hasła",
+ "auth.forgot_password_send_email": "Wyślij email resetujący hasło",
+ "auth.register_now": "Zarejestruj",
+ "auth.logout": "Wyloguj",
+ "auth.register": "Zarejestruj",
+ "auth.registration_disabled": "Rejestracja jest wyłączona. Skontaktuj się z administratorem.",
+ "auth.reset_password": "Zresetuj hasło",
+ "auth.failed": "Podane dane nie zgadzają się z naszymi rekordami.",
+ "auth.failed.callback": "Nie udało się przeprocesować callbacku od dostawcy logowania.",
+ "auth.failed.password": "Podane hasło jest nieprawidłowe.",
+ "auth.failed.email": "Nie znaleziono użytkownika z takim adresem email.",
+ "auth.throttle": "Zbyt wiele prób logowania. Spróbuj ponownie za :seconds sekund.",
+ "input.name": "Nazwa",
+ "input.email": "Email",
+ "input.password": "Hasło",
+ "input.password.again": "Hasło ponownie",
+ "input.code": "Jednorazowy kod",
+ "input.recovery_code": "Kod odzyskiwania",
+ "button.save": "Zapisz",
+ "repository.url": "Przykłady
Dla publicznych repozytoriów użyj https://....
Dla prywatnych repozytoriów, użyj git@....
https://github.com/coollabsio/coolify-examples - zostanie wybrany branch main
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify - zostanie wybrany branch nodejs-fastify
https://gitea.com/sedlav/expressjs.git - zostanie wybrany branch main
https://gitlab.com/andrasbacsai/nodejs-example.git - zostanie wybrany branch main",
+ "service.stop": "Ten serwis zostanie zatrzymany.",
+ "resource.docker_cleanup": "Uruchom Docker Cleanup (usunie nieużywane obrazy i cache buildera).",
+ "resource.non_persistent": "Wszystkie nietrwałe dane zostaną usunięte.",
+ "resource.delete_volumes": "Trwale usuń wszystkie wolumeny powiązane z tym zasobem.",
+ "resource.delete_connected_networks": "Trwale usuń wszystkie niepredefiniowane sieci powiązane z tym zasobem.",
+ "resource.delete_configurations": "Trwale usuń wszystkie pliki konfiguracyjne z serwera.",
+ "database.delete_backups_locally": "Wszystkie backupy zostaną trwale usunięte z lokalnej pamięci.",
+ "warning.sslipdomain": "Twoja konfiguracja została zapisana, lecz domena sslip z https jest NIEZALECANA, ponieważ serwery Let's Encrypt z tą publiczną domeną są pod rate limitem (walidacja certyfikatu SSL certificate się nie powiedzie).
Lepiej użyj własnej domeny."
+}
\ No newline at end of file
diff --git a/lang/pt-br.json b/lang/pt-br.json
index c3a102995..f3ebb6c69 100644
--- a/lang/pt-br.json
+++ b/lang/pt-br.json
@@ -11,7 +11,8 @@
"auth.login.infomaniak": "Entrar com Infomaniak",
"auth.already_registered": "Já tem uma conta?",
"auth.confirm_password": "Confirmar senha",
- "auth.forgot_password": "Esqueceu a senha",
+ "auth.forgot_password_link": "Esqueceu a senha?",
+ "auth.forgot_password_heading": "Recuperação de senha",
"auth.forgot_password_send_email": "Enviar e-mail para redefinir senha",
"auth.register_now": "Cadastre-se",
"auth.logout": "Sair",
@@ -39,4 +40,4 @@
"resource.delete_configurations": "Excluir permanentemente todos os arquivos de configuração do servidor.",
"database.delete_backups_locally": "Todos os backups serão excluídos permanentemente do armazenamento local.",
"warning.sslipdomain": "Sua configuração foi salva, mas o domínio sslip com https NÃO é recomendado, porque os servidores do Let's Encrypt com este domínio público têm limitação de taxa (a validação do certificado SSL falhará).
Use seu próprio domínio em vez disso."
-}
+}
\ No newline at end of file
diff --git a/lang/pt.json b/lang/pt.json
index 80ff8c146..08ad19df3 100644
--- a/lang/pt.json
+++ b/lang/pt.json
@@ -10,7 +10,8 @@
"auth.login.infomaniak": "Entrar com Infomaniak",
"auth.already_registered": "Já tem uma conta?",
"auth.confirm_password": "Confirmar senha",
- "auth.forgot_password": "Esqueceu a senha?",
+ "auth.forgot_password_link": "Esqueceu a senha?",
+ "auth.forgot_password_heading": "Recuperação de senha",
"auth.forgot_password_send_email": "Enviar e-mail de redefinição de senha",
"auth.register_now": "Cadastrar-se",
"auth.logout": "Sair",
@@ -30,4 +31,4 @@
"input.recovery_code": "Código de recuperação",
"button.save": "Salvar",
"repository.url": "Exemplos
Para repositórios públicos, use https://....
Para repositórios privados, use git@....
https://github.com/coollabsio/coolify-examples a branch main será selecionada
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify a branch nodejs-fastify será selecionada.
https://gitea.com/sedlav/expressjs.git a branch main será selecionada.
https://gitlab.com/andrasbacsai/nodejs-example.git a branch main será selecionada."
-}
+}
\ No newline at end of file
diff --git a/lang/ro.json b/lang/ro.json
index 5588ea6f4..18028d087 100644
--- a/lang/ro.json
+++ b/lang/ro.json
@@ -10,7 +10,8 @@
"auth.login.infomaniak": "Autentificare prin Infomaniak",
"auth.already_registered": "Sunteți deja înregistrat?",
"auth.confirm_password": "Confirmați parola",
- "auth.forgot_password": "Ați uitat parola",
+ "auth.forgot_password_link": "Ați uitat parola?",
+ "auth.forgot_password_heading": "Recuperare parolă",
"auth.forgot_password_send_email": "Trimiteți e-mail-ul pentru resetarea parolei",
"auth.register_now": "Înregistrare",
"auth.logout": "Deconectare",
@@ -37,4 +38,4 @@
"resource.delete_connected_networks": "Ștergeți definitiv toate rețelele non-predefinite asociate cu această resursă.",
"resource.delete_configurations": "Ștergeți definitiv toate fișierele de configurare de pe server.",
"database.delete_backups_locally": "Toate copiile de rezervă vor fi șterse definitiv din stocarea locală."
-}
+}
\ No newline at end of file
diff --git a/lang/tr.json b/lang/tr.json
index 74f693dc9..e3f34aa14 100644
--- a/lang/tr.json
+++ b/lang/tr.json
@@ -10,7 +10,8 @@
"auth.login.infomaniak": "Infomaniak ile Giriş Yap",
"auth.already_registered": "Zaten kayıtlı mısınız?",
"auth.confirm_password": "Şifreyi Onayla",
- "auth.forgot_password": "Şifremi Unuttum",
+ "auth.forgot_password_link": "Şifrenizi mi unuttunuz?",
+ "auth.forgot_password_heading": "Şifre Kurtarma",
"auth.forgot_password_send_email": "Şifre sıfırlama e-postası gönder",
"auth.register_now": "Kayıt Ol",
"auth.logout": "Çıkış Yap",
@@ -38,4 +39,4 @@
"resource.delete_configurations": "Sunucudaki tüm yapılandırma dosyaları kalıcı olarak silinecek.",
"database.delete_backups_locally": "Tüm yedekler yerel depolamadan kalıcı olarak silinecek.",
"warning.sslipdomain": "Yapılandırmanız kaydedildi, ancak sslip domain ile https ÖNERİLMEZ, çünkü Let's Encrypt sunucuları bu genel domain ile sınırlandırılmıştır (SSL sertifikası doğrulaması başarısız olur).
Bunun yerine kendi domaininizi kullanın."
-}
+}
\ No newline at end of file
diff --git a/lang/vi.json b/lang/vi.json
index 46edac599..76e380477 100644
--- a/lang/vi.json
+++ b/lang/vi.json
@@ -10,7 +10,8 @@
"auth.login.infomaniak": "Đăng Nhập Bằng Infomaniak",
"auth.already_registered": "Đã đăng ký?",
"auth.confirm_password": "Nhập lại mật khẩu",
- "auth.forgot_password": "Quên mật khẩu",
+ "auth.forgot_password_link": "Quên mật khẩu?",
+ "auth.forgot_password_heading": "Khôi phục mật khẩu",
"auth.forgot_password_send_email": "Gửi email đặt lại mật khẩu",
"auth.register_now": "Đăng ký ngay",
"auth.logout": "Đăng xuất",
@@ -30,4 +31,4 @@
"input.recovery_code": "Mã khôi phục",
"button.save": "Lưu",
"repository.url": "Ví dụ
Với repo công khai, sử dụng https://....
Với repo riêng tư, sử dụng git@....
https://github.com/coollabsio/coolify-examples nhánh chính sẽ được chọn
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nhánh nodejs-fastify sẽ được chọn.
https://gitea.com/sedlav/expressjs.git nhánh chính sẽ được chọn.
https://gitlab.com/andrasbacsai/nodejs-example.git nhánh chính sẽ được chọn."
-}
+}
\ No newline at end of file
diff --git a/lang/zh-cn.json b/lang/zh-cn.json
index d46c71e07..530621ee1 100644
--- a/lang/zh-cn.json
+++ b/lang/zh-cn.json
@@ -10,7 +10,8 @@
"auth.login.infomaniak": "使用 Infomaniak 登录",
"auth.already_registered": "已经注册?",
"auth.confirm_password": "确认密码",
- "auth.forgot_password": "忘记密码",
+ "auth.forgot_password_link": "忘记密码?",
+ "auth.forgot_password_heading": "密码找回",
"auth.forgot_password_send_email": "发送密码重置邮件",
"auth.register_now": "注册",
"auth.logout": "退出登录",
@@ -30,4 +31,4 @@
"input.recovery_code": "恢复码",
"button.save": "保存",
"repository.url": "示例
对于公共代码仓库,请使用 https://...。
对于私有代码仓库,请使用 git@...。
https://github.com/coollabsio/coolify-examples main 分支将被选择
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify 分支将被选择。
https://gitea.com/sedlav/expressjs.git main 分支将被选择。
https://gitlab.com/andrasbacsai/nodejs-example.git main 分支将被选择"
-}
+}
\ No newline at end of file
diff --git a/lang/zh-tw.json b/lang/zh-tw.json
index c0784c7b7..aa078104b 100644
--- a/lang/zh-tw.json
+++ b/lang/zh-tw.json
@@ -10,7 +10,8 @@
"auth.login.infomaniak": "使用 Infomaniak 登入",
"auth.already_registered": "已經註冊?",
"auth.confirm_password": "確認密碼",
- "auth.forgot_password": "忘記密碼",
+ "auth.forgot_password_link": "忘記密碼?",
+ "auth.forgot_password_heading": "密碼找回",
"auth.forgot_password_send_email": "發送重設密碼電郵",
"auth.register_now": "註冊",
"auth.logout": "登出",
@@ -30,4 +31,4 @@
"input.recovery_code": "恢復碼",
"button.save": "儲存",
"repository.url": "例子
對於公共代碼倉庫,請使用 https://...。
對於私有代碼倉庫,請使用 git@...。
https://github.com/coollabsio/coolify-examples main 分支將被選擇
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify 分支將被選擇。
https://gitea.com/sedlav/expressjs.git main 分支將被選擇。
https://gitlab.com/andrasbacsai/nodejs-example.git main 分支將被選擇。"
-}
+}
\ No newline at end of file
diff --git a/other/nightly/install.sh b/other/nightly/install.sh
index e9f54952a..92ad12302 100755
--- a/other/nightly/install.sh
+++ b/other/nightly/install.sh
@@ -253,6 +253,11 @@ if [ "$OS_TYPE" = "endeavouros" ]; then
OS_TYPE="arch"
fi
+# Check if the OS is Cachy OS, if so, change it to arch
+if [ "$OS_TYPE" = "cachyos" ]; then
+ OS_TYPE="arch"
+fi
+
# Check if the OS is Asahi Linux, if so, change it to fedora
if [ "$OS_TYPE" = "fedora-asahi-remix" ]; then
OS_TYPE="fedora"
@@ -844,7 +849,7 @@ IPV6_PUBLIC_IP=$(curl -6s https://ifconfig.io || true)
echo -e "\nYour instance is ready to use!\n"
if [ -n "$IPV4_PUBLIC_IP" ]; then
- echo -e "You can access Coolify through your Public IPV4: http://$(curl -4s https://ifconfig.io):8000"
+ echo -e "You can access Coolify through your Public IPV4: http://$IPV4_PUBLIC_IP:8000"
fi
if [ -n "$IPV6_PUBLIC_IP" ]; then
echo -e "You can access Coolify through your Public IPv6: http://[$IPV6_PUBLIC_IP]:8000"
diff --git a/package-lock.json b/package-lock.json
index d86caea87..34b2c1dd5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -74,13 +74,13 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.27.5",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz",
- "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
+ "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.27.3"
+ "@babel/types": "^7.28.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -90,9 +90,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.27.6",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz",
- "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==",
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -104,9 +104,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
- "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
+ "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
"cpu": [
"ppc64"
],
@@ -121,9 +121,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz",
- "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
+ "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
"cpu": [
"arm"
],
@@ -138,9 +138,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz",
- "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
+ "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
"cpu": [
"arm64"
],
@@ -155,9 +155,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz",
- "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
+ "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
"cpu": [
"x64"
],
@@ -172,9 +172,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz",
- "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
+ "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
"cpu": [
"arm64"
],
@@ -189,9 +189,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz",
- "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
+ "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
"cpu": [
"x64"
],
@@ -206,9 +206,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz",
- "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
"cpu": [
"arm64"
],
@@ -223,9 +223,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz",
- "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
+ "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
"cpu": [
"x64"
],
@@ -240,9 +240,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz",
- "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
+ "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
"cpu": [
"arm"
],
@@ -257,9 +257,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz",
- "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
+ "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
"cpu": [
"arm64"
],
@@ -274,9 +274,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz",
- "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
+ "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
"cpu": [
"ia32"
],
@@ -291,9 +291,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz",
- "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
+ "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
"cpu": [
"loong64"
],
@@ -308,9 +308,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz",
- "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
+ "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
"cpu": [
"mips64el"
],
@@ -325,9 +325,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz",
- "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
+ "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
"cpu": [
"ppc64"
],
@@ -342,9 +342,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz",
- "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
+ "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
"cpu": [
"riscv64"
],
@@ -359,9 +359,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz",
- "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
+ "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
"cpu": [
"s390x"
],
@@ -376,9 +376,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz",
- "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
+ "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
"cpu": [
"x64"
],
@@ -393,9 +393,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
- "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
"cpu": [
"arm64"
],
@@ -410,9 +410,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz",
- "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
+ "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
"cpu": [
"x64"
],
@@ -427,9 +427,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
- "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
"cpu": [
"arm64"
],
@@ -444,9 +444,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz",
- "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
+ "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
"cpu": [
"x64"
],
@@ -460,10 +460,27 @@
"node": ">=18"
}
},
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
+ "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/sunos-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz",
- "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
+ "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
"cpu": [
"x64"
],
@@ -478,9 +495,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz",
- "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
+ "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
"cpu": [
"arm64"
],
@@ -495,9 +512,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz",
- "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
+ "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
"cpu": [
"ia32"
],
@@ -512,9 +529,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz",
- "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
+ "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
"cpu": [
"x64"
],
@@ -529,9 +546,9 @@
}
},
"node_modules/@ioredis/commands": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
- "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.0.tgz",
+ "integrity": "sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==",
"license": "MIT"
},
"node_modules/@isaacs/fs-minipass": {
@@ -548,18 +565,14 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
- "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@@ -572,27 +585,17 @@
"node": ">=6.0.0"
}
},
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "version": "0.3.29",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -601,9 +604,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz",
- "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
+ "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==",
"cpu": [
"arm"
],
@@ -615,9 +618,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz",
- "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz",
+ "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==",
"cpu": [
"arm64"
],
@@ -629,9 +632,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz",
- "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz",
+ "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==",
"cpu": [
"arm64"
],
@@ -643,9 +646,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz",
- "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz",
+ "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==",
"cpu": [
"x64"
],
@@ -657,9 +660,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz",
- "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz",
+ "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==",
"cpu": [
"arm64"
],
@@ -671,9 +674,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz",
- "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz",
+ "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==",
"cpu": [
"x64"
],
@@ -685,9 +688,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz",
- "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz",
+ "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==",
"cpu": [
"arm"
],
@@ -699,9 +702,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz",
- "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz",
+ "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==",
"cpu": [
"arm"
],
@@ -713,9 +716,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz",
- "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz",
+ "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==",
"cpu": [
"arm64"
],
@@ -727,9 +730,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz",
- "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz",
+ "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==",
"cpu": [
"arm64"
],
@@ -741,9 +744,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz",
- "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz",
+ "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==",
"cpu": [
"loong64"
],
@@ -754,10 +757,10 @@
"linux"
]
},
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz",
- "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==",
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz",
+ "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==",
"cpu": [
"ppc64"
],
@@ -769,9 +772,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz",
- "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz",
+ "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==",
"cpu": [
"riscv64"
],
@@ -783,9 +786,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz",
- "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz",
+ "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==",
"cpu": [
"riscv64"
],
@@ -797,9 +800,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz",
- "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz",
+ "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==",
"cpu": [
"s390x"
],
@@ -811,9 +814,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz",
- "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz",
+ "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==",
"cpu": [
"x64"
],
@@ -825,9 +828,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz",
- "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz",
+ "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==",
"cpu": [
"x64"
],
@@ -839,9 +842,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz",
- "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz",
+ "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==",
"cpu": [
"arm64"
],
@@ -853,9 +856,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz",
- "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz",
+ "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==",
"cpu": [
"ia32"
],
@@ -867,9 +870,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz",
- "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz",
+ "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==",
"cpu": [
"x64"
],
@@ -1192,9 +1195,9 @@
}
},
"node_modules/@types/estree": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
- "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
@@ -1544,9 +1547,9 @@
}
},
"node_modules/enhanced-resolve": {
- "version": "5.18.1",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
- "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
+ "version": "5.18.2",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
+ "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1620,9 +1623,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.25.4",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
- "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
+ "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -1633,31 +1636,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.4",
- "@esbuild/android-arm": "0.25.4",
- "@esbuild/android-arm64": "0.25.4",
- "@esbuild/android-x64": "0.25.4",
- "@esbuild/darwin-arm64": "0.25.4",
- "@esbuild/darwin-x64": "0.25.4",
- "@esbuild/freebsd-arm64": "0.25.4",
- "@esbuild/freebsd-x64": "0.25.4",
- "@esbuild/linux-arm": "0.25.4",
- "@esbuild/linux-arm64": "0.25.4",
- "@esbuild/linux-ia32": "0.25.4",
- "@esbuild/linux-loong64": "0.25.4",
- "@esbuild/linux-mips64el": "0.25.4",
- "@esbuild/linux-ppc64": "0.25.4",
- "@esbuild/linux-riscv64": "0.25.4",
- "@esbuild/linux-s390x": "0.25.4",
- "@esbuild/linux-x64": "0.25.4",
- "@esbuild/netbsd-arm64": "0.25.4",
- "@esbuild/netbsd-x64": "0.25.4",
- "@esbuild/openbsd-arm64": "0.25.4",
- "@esbuild/openbsd-x64": "0.25.4",
- "@esbuild/sunos-x64": "0.25.4",
- "@esbuild/win32-arm64": "0.25.4",
- "@esbuild/win32-ia32": "0.25.4",
- "@esbuild/win32-x64": "0.25.4"
+ "@esbuild/aix-ppc64": "0.25.8",
+ "@esbuild/android-arm": "0.25.8",
+ "@esbuild/android-arm64": "0.25.8",
+ "@esbuild/android-x64": "0.25.8",
+ "@esbuild/darwin-arm64": "0.25.8",
+ "@esbuild/darwin-x64": "0.25.8",
+ "@esbuild/freebsd-arm64": "0.25.8",
+ "@esbuild/freebsd-x64": "0.25.8",
+ "@esbuild/linux-arm": "0.25.8",
+ "@esbuild/linux-arm64": "0.25.8",
+ "@esbuild/linux-ia32": "0.25.8",
+ "@esbuild/linux-loong64": "0.25.8",
+ "@esbuild/linux-mips64el": "0.25.8",
+ "@esbuild/linux-ppc64": "0.25.8",
+ "@esbuild/linux-riscv64": "0.25.8",
+ "@esbuild/linux-s390x": "0.25.8",
+ "@esbuild/linux-x64": "0.25.8",
+ "@esbuild/netbsd-arm64": "0.25.8",
+ "@esbuild/netbsd-x64": "0.25.8",
+ "@esbuild/openbsd-arm64": "0.25.8",
+ "@esbuild/openbsd-x64": "0.25.8",
+ "@esbuild/openharmony-arm64": "0.25.8",
+ "@esbuild/sunos-x64": "0.25.8",
+ "@esbuild/win32-arm64": "0.25.8",
+ "@esbuild/win32-ia32": "0.25.8",
+ "@esbuild/win32-x64": "0.25.8"
}
},
"node_modules/estree-walker": {
@@ -1668,9 +1672,9 @@
"license": "MIT"
},
"node_modules/fdir": {
- "version": "6.4.4",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
- "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -1683,9 +1687,9 @@
}
},
"node_modules/follow-redirects": {
- "version": "1.15.9",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
- "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"dev": true,
"funding": [
{
@@ -1704,15 +1708,16 @@
}
},
"node_modules/form-data": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
- "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -1870,9 +1875,9 @@
}
},
"node_modules/jiti": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
- "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
+ "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
"dev": true,
"license": "MIT",
"bin": {
@@ -2306,9 +2311,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2392,9 +2397,9 @@
}
},
"node_modules/react": {
- "version": "19.1.0",
- "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
- "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
+ "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -2424,13 +2429,13 @@
}
},
"node_modules/rollup": {
- "version": "4.40.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz",
- "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==",
+ "version": "4.46.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz",
+ "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/estree": "1.0.7"
+ "@types/estree": "1.0.8"
},
"bin": {
"rollup": "dist/bin/rollup"
@@ -2440,26 +2445,26 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.40.2",
- "@rollup/rollup-android-arm64": "4.40.2",
- "@rollup/rollup-darwin-arm64": "4.40.2",
- "@rollup/rollup-darwin-x64": "4.40.2",
- "@rollup/rollup-freebsd-arm64": "4.40.2",
- "@rollup/rollup-freebsd-x64": "4.40.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.40.2",
- "@rollup/rollup-linux-arm-musleabihf": "4.40.2",
- "@rollup/rollup-linux-arm64-gnu": "4.40.2",
- "@rollup/rollup-linux-arm64-musl": "4.40.2",
- "@rollup/rollup-linux-loongarch64-gnu": "4.40.2",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.40.2",
- "@rollup/rollup-linux-riscv64-musl": "4.40.2",
- "@rollup/rollup-linux-s390x-gnu": "4.40.2",
- "@rollup/rollup-linux-x64-gnu": "4.40.2",
- "@rollup/rollup-linux-x64-musl": "4.40.2",
- "@rollup/rollup-win32-arm64-msvc": "4.40.2",
- "@rollup/rollup-win32-ia32-msvc": "4.40.2",
- "@rollup/rollup-win32-x64-msvc": "4.40.2",
+ "@rollup/rollup-android-arm-eabi": "4.46.2",
+ "@rollup/rollup-android-arm64": "4.46.2",
+ "@rollup/rollup-darwin-arm64": "4.46.2",
+ "@rollup/rollup-darwin-x64": "4.46.2",
+ "@rollup/rollup-freebsd-arm64": "4.46.2",
+ "@rollup/rollup-freebsd-x64": "4.46.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.46.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.46.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.46.2",
+ "@rollup/rollup-linux-arm64-musl": "4.46.2",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.46.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.46.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.46.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.46.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.46.2",
+ "@rollup/rollup-linux-x64-gnu": "4.46.2",
+ "@rollup/rollup-linux-x64-musl": "4.46.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.46.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.46.2",
+ "@rollup/rollup-win32-x64-msvc": "4.46.2",
"fsevents": "~2.3.2"
}
},
@@ -2600,9 +2605,9 @@
}
},
"node_modules/tinyglobby": {
- "version": "0.2.13",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
- "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/public/js/purify.min.js b/public/js/purify.min.js
new file mode 100644
index 000000000..73df78d60
--- /dev/null
+++ b/public/js/purify.min.js
@@ -0,0 +1,3 @@
+/*! @license DOMPurify 3.2.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.6/LICENSE */
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,(function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:n,getPrototypeOf:o,getOwnPropertyDescriptor:r}=Object;let{freeze:i,seal:a,create:l}=Object,{apply:c,construct:s}="undefined"!=typeof Reflect&&Reflect;i||(i=function(e){return e}),a||(a=function(e){return e}),c||(c=function(e,t,n){return e.apply(t,n)}),s||(s=function(e,t){return new e(...t)});const u=R(Array.prototype.forEach),m=R(Array.prototype.lastIndexOf),p=R(Array.prototype.pop),f=R(Array.prototype.push),d=R(Array.prototype.splice),h=R(String.prototype.toLowerCase),g=R(String.prototype.toString),T=R(String.prototype.match),y=R(String.prototype.replace),E=R(String.prototype.indexOf),A=R(String.prototype.trim),_=R(Object.prototype.hasOwnProperty),S=R(RegExp.prototype.test),b=(N=TypeError,function(){for(var e=arguments.length,t=new Array(e),n=0;n1?n-1:0),r=1;r2&&void 0!==arguments[2]?arguments[2]:h;t&&t(e,null);let i=o.length;for(;i--;){let t=o[i];if("string"==typeof t){const e=r(t);e!==t&&(n(o)||(o[i]=e),t=e)}e[t]=!0}return e}function O(e){for(let t=0;t/gm),G=a(/\$\{[\w\W]*/gm),Y=a(/^data-[\-\w.\u00B7-\uFFFF]+$/),j=a(/^aria-[\-\w]+$/),X=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),q=a(/^(?:\w+script|data):/i),$=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),K=a(/^html$/i),V=a(/^[a-z][.\w]*(-[.\w]+)+$/i);var Z=Object.freeze({__proto__:null,ARIA_ATTR:j,ATTR_WHITESPACE:$,CUSTOM_ELEMENT:V,DATA_ATTR:Y,DOCTYPE_NAME:K,ERB_EXPR:W,IS_ALLOWED_URI:X,IS_SCRIPT_OR_DATA:q,MUSTACHE_EXPR:B,TMPLIT_EXPR:G});const J=1,Q=3,ee=7,te=8,ne=9,oe=function(){return"undefined"==typeof window?null:window};var re=function t(){let n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:oe();const o=e=>t(e);if(o.version="3.2.6",o.removed=[],!n||!n.document||n.document.nodeType!==ne||!n.Element)return o.isSupported=!1,o;let{document:r}=n;const a=r,c=a.currentScript,{DocumentFragment:s,HTMLTemplateElement:N,Node:R,Element:O,NodeFilter:B,NamedNodeMap:W=n.NamedNodeMap||n.MozNamedAttrMap,HTMLFormElement:G,DOMParser:Y,trustedTypes:j}=n,q=O.prototype,$=v(q,"cloneNode"),V=v(q,"remove"),re=v(q,"nextSibling"),ie=v(q,"childNodes"),ae=v(q,"parentNode");if("function"==typeof N){const e=r.createElement("template");e.content&&e.content.ownerDocument&&(r=e.content.ownerDocument)}let le,ce="";const{implementation:se,createNodeIterator:ue,createDocumentFragment:me,getElementsByTagName:pe}=r,{importNode:fe}=a;let de={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]};o.isSupported="function"==typeof e&&"function"==typeof ae&&se&&void 0!==se.createHTMLDocument;const{MUSTACHE_EXPR:he,ERB_EXPR:ge,TMPLIT_EXPR:Te,DATA_ATTR:ye,ARIA_ATTR:Ee,IS_SCRIPT_OR_DATA:Ae,ATTR_WHITESPACE:_e,CUSTOM_ELEMENT:Se}=Z;let{IS_ALLOWED_URI:be}=Z,Ne=null;const Re=w({},[...L,...C,...x,...M,...U]);let we=null;const Oe=w({},[...z,...P,...H,...F]);let De=Object.seal(l(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),ve=null,Le=null,Ce=!0,xe=!0,Ie=!1,Me=!0,ke=!1,Ue=!0,ze=!1,Pe=!1,He=!1,Fe=!1,Be=!1,We=!1,Ge=!0,Ye=!1,je=!0,Xe=!1,qe={},$e=null;const Ke=w({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let Ve=null;const Ze=w({},["audio","video","img","source","image","track"]);let Je=null;const Qe=w({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),et="http://www.w3.org/1998/Math/MathML",tt="http://www.w3.org/2000/svg",nt="http://www.w3.org/1999/xhtml";let ot=nt,rt=!1,it=null;const at=w({},[et,tt,nt],g);let lt=w({},["mi","mo","mn","ms","mtext"]),ct=w({},["annotation-xml"]);const st=w({},["title","style","font","a","script"]);let ut=null;const mt=["application/xhtml+xml","text/html"];let pt=null,ft=null;const dt=r.createElement("form"),ht=function(e){return e instanceof RegExp||e instanceof Function},gt=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!ft||ft!==e){if(e&&"object"==typeof e||(e={}),e=D(e),ut=-1===mt.indexOf(e.PARSER_MEDIA_TYPE)?"text/html":e.PARSER_MEDIA_TYPE,pt="application/xhtml+xml"===ut?g:h,Ne=_(e,"ALLOWED_TAGS")?w({},e.ALLOWED_TAGS,pt):Re,we=_(e,"ALLOWED_ATTR")?w({},e.ALLOWED_ATTR,pt):Oe,it=_(e,"ALLOWED_NAMESPACES")?w({},e.ALLOWED_NAMESPACES,g):at,Je=_(e,"ADD_URI_SAFE_ATTR")?w(D(Qe),e.ADD_URI_SAFE_ATTR,pt):Qe,Ve=_(e,"ADD_DATA_URI_TAGS")?w(D(Ze),e.ADD_DATA_URI_TAGS,pt):Ze,$e=_(e,"FORBID_CONTENTS")?w({},e.FORBID_CONTENTS,pt):Ke,ve=_(e,"FORBID_TAGS")?w({},e.FORBID_TAGS,pt):D({}),Le=_(e,"FORBID_ATTR")?w({},e.FORBID_ATTR,pt):D({}),qe=!!_(e,"USE_PROFILES")&&e.USE_PROFILES,Ce=!1!==e.ALLOW_ARIA_ATTR,xe=!1!==e.ALLOW_DATA_ATTR,Ie=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Me=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,ke=e.SAFE_FOR_TEMPLATES||!1,Ue=!1!==e.SAFE_FOR_XML,ze=e.WHOLE_DOCUMENT||!1,Fe=e.RETURN_DOM||!1,Be=e.RETURN_DOM_FRAGMENT||!1,We=e.RETURN_TRUSTED_TYPE||!1,He=e.FORCE_BODY||!1,Ge=!1!==e.SANITIZE_DOM,Ye=e.SANITIZE_NAMED_PROPS||!1,je=!1!==e.KEEP_CONTENT,Xe=e.IN_PLACE||!1,be=e.ALLOWED_URI_REGEXP||X,ot=e.NAMESPACE||nt,lt=e.MATHML_TEXT_INTEGRATION_POINTS||lt,ct=e.HTML_INTEGRATION_POINTS||ct,De=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&ht(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(De.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&ht(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(De.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(De.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),ke&&(xe=!1),Be&&(Fe=!0),qe&&(Ne=w({},U),we=[],!0===qe.html&&(w(Ne,L),w(we,z)),!0===qe.svg&&(w(Ne,C),w(we,P),w(we,F)),!0===qe.svgFilters&&(w(Ne,x),w(we,P),w(we,F)),!0===qe.mathMl&&(w(Ne,M),w(we,H),w(we,F))),e.ADD_TAGS&&(Ne===Re&&(Ne=D(Ne)),w(Ne,e.ADD_TAGS,pt)),e.ADD_ATTR&&(we===Oe&&(we=D(we)),w(we,e.ADD_ATTR,pt)),e.ADD_URI_SAFE_ATTR&&w(Je,e.ADD_URI_SAFE_ATTR,pt),e.FORBID_CONTENTS&&($e===Ke&&($e=D($e)),w($e,e.FORBID_CONTENTS,pt)),je&&(Ne["#text"]=!0),ze&&w(Ne,["html","head","body"]),Ne.table&&(w(Ne,["tbody"]),delete ve.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw b('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw b('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');le=e.TRUSTED_TYPES_POLICY,ce=le.createHTML("")}else void 0===le&&(le=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let n=null;const o="data-tt-policy-suffix";t&&t.hasAttribute(o)&&(n=t.getAttribute(o));const r="dompurify"+(n?"#"+n:"");try{return e.createPolicy(r,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}}(j,c)),null!==le&&"string"==typeof ce&&(ce=le.createHTML(""));i&&i(e),ft=e}},Tt=w({},[...C,...x,...I]),yt=w({},[...M,...k]),Et=function(e){f(o.removed,{element:e});try{ae(e).removeChild(e)}catch(t){V(e)}},At=function(e,t){try{f(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){f(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e)if(Fe||Be)try{Et(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},_t=function(e){let t=null,n=null;if(He)e=" "+e;else{const t=T(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===ut&&ot===nt&&(e=''+e+"");const o=le?le.createHTML(e):e;if(ot===nt)try{t=(new Y).parseFromString(o,ut)}catch(e){}if(!t||!t.documentElement){t=se.createDocument(ot,"template",null);try{t.documentElement.innerHTML=rt?ce:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),ot===nt?pe.call(t,ze?"html":"body")[0]:ze?t.documentElement:i},St=function(e){return ue.call(e.ownerDocument||e,e,B.SHOW_ELEMENT|B.SHOW_COMMENT|B.SHOW_TEXT|B.SHOW_PROCESSING_INSTRUCTION|B.SHOW_CDATA_SECTION,null)},bt=function(e){return e instanceof G&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof W)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},Nt=function(e){return"function"==typeof R&&e instanceof R};function Rt(e,t,n){u(e,(e=>{e.call(o,t,n,ft)}))}const wt=function(e){let t=null;if(Rt(de.beforeSanitizeElements,e,null),bt(e))return Et(e),!0;const n=pt(e.nodeName);if(Rt(de.uponSanitizeElement,e,{tagName:n,allowedTags:Ne}),Ue&&e.hasChildNodes()&&!Nt(e.firstElementChild)&&S(/<[/\w!]/g,e.innerHTML)&&S(/<[/\w!]/g,e.textContent))return Et(e),!0;if(e.nodeType===ee)return Et(e),!0;if(Ue&&e.nodeType===te&&S(/<[/\w]/g,e.data))return Et(e),!0;if(!Ne[n]||ve[n]){if(!ve[n]&&Dt(n)){if(De.tagNameCheck instanceof RegExp&&S(De.tagNameCheck,n))return!1;if(De.tagNameCheck instanceof Function&&De.tagNameCheck(n))return!1}if(je&&!$e[n]){const t=ae(e)||e.parentNode,n=ie(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o){const r=$(n[o],!0);r.__removalCount=(e.__removalCount||0)+1,t.insertBefore(r,re(e))}}}return Et(e),!0}return e instanceof O&&!function(e){let t=ae(e);t&&t.tagName||(t={namespaceURI:ot,tagName:"template"});const n=h(e.tagName),o=h(t.tagName);return!!it[e.namespaceURI]&&(e.namespaceURI===tt?t.namespaceURI===nt?"svg"===n:t.namespaceURI===et?"svg"===n&&("annotation-xml"===o||lt[o]):Boolean(Tt[n]):e.namespaceURI===et?t.namespaceURI===nt?"math"===n:t.namespaceURI===tt?"math"===n&&ct[o]:Boolean(yt[n]):e.namespaceURI===nt?!(t.namespaceURI===tt&&!ct[o])&&!(t.namespaceURI===et&&!lt[o])&&!yt[n]&&(st[n]||!Tt[n]):!("application/xhtml+xml"!==ut||!it[e.namespaceURI]))}(e)?(Et(e),!0):"noscript"!==n&&"noembed"!==n&&"noframes"!==n||!S(/<\/no(script|embed|frames)/i,e.innerHTML)?(ke&&e.nodeType===Q&&(t=e.textContent,u([he,ge,Te],(e=>{t=y(t,e," ")})),e.textContent!==t&&(f(o.removed,{element:e.cloneNode()}),e.textContent=t)),Rt(de.afterSanitizeElements,e,null),!1):(Et(e),!0)},Ot=function(e,t,n){if(Ge&&("id"===t||"name"===t)&&(n in r||n in dt))return!1;if(xe&&!Le[t]&&S(ye,t));else if(Ce&&S(Ee,t));else if(!we[t]||Le[t]){if(!(Dt(e)&&(De.tagNameCheck instanceof RegExp&&S(De.tagNameCheck,e)||De.tagNameCheck instanceof Function&&De.tagNameCheck(e))&&(De.attributeNameCheck instanceof RegExp&&S(De.attributeNameCheck,t)||De.attributeNameCheck instanceof Function&&De.attributeNameCheck(t))||"is"===t&&De.allowCustomizedBuiltInElements&&(De.tagNameCheck instanceof RegExp&&S(De.tagNameCheck,n)||De.tagNameCheck instanceof Function&&De.tagNameCheck(n))))return!1}else if(Je[t]);else if(S(be,y(n,_e,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==E(n,"data:")||!Ve[e]){if(Ie&&!S(Ae,y(n,_e,"")));else if(n)return!1}else;return!0},Dt=function(e){return"annotation-xml"!==e&&T(e,Se)},vt=function(e){Rt(de.beforeSanitizeAttributes,e,null);const{attributes:t}=e;if(!t||bt(e))return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:we,forceKeepAttr:void 0};let r=t.length;for(;r--;){const i=t[r],{name:a,namespaceURI:l,value:c}=i,s=pt(a),m=c;let f="value"===a?m:A(m);if(n.attrName=s,n.attrValue=f,n.keepAttr=!0,n.forceKeepAttr=void 0,Rt(de.uponSanitizeAttribute,e,n),f=n.attrValue,!Ye||"id"!==s&&"name"!==s||(At(a,e),f="user-content-"+f),Ue&&S(/((--!?|])>)|<\/(style|title)/i,f)){At(a,e);continue}if(n.forceKeepAttr)continue;if(!n.keepAttr){At(a,e);continue}if(!Me&&S(/\/>/i,f)){At(a,e);continue}ke&&u([he,ge,Te],(e=>{f=y(f,e," ")}));const d=pt(e.nodeName);if(Ot(d,s,f)){if(le&&"object"==typeof j&&"function"==typeof j.getAttributeType)if(l);else switch(j.getAttributeType(d,s)){case"TrustedHTML":f=le.createHTML(f);break;case"TrustedScriptURL":f=le.createScriptURL(f)}if(f!==m)try{l?e.setAttributeNS(l,a,f):e.setAttribute(a,f),bt(e)?Et(e):p(o.removed)}catch(t){At(a,e)}}else At(a,e)}Rt(de.afterSanitizeAttributes,e,null)},Lt=function e(t){let n=null;const o=St(t);for(Rt(de.beforeSanitizeShadowDOM,t,null);n=o.nextNode();)Rt(de.uponSanitizeShadowNode,n,null),wt(n),vt(n),n.content instanceof s&&e(n.content);Rt(de.afterSanitizeShadowDOM,t,null)};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,i=null,l=null;if(rt=!e,rt&&(e="\x3c!--\x3e"),"string"!=typeof e&&!Nt(e)){if("function"!=typeof e.toString)throw b("toString is not a function");if("string"!=typeof(e=e.toString()))throw b("dirty is not a string, aborting")}if(!o.isSupported)return e;if(Pe||gt(t),o.removed=[],"string"==typeof e&&(Xe=!1),Xe){if(e.nodeName){const t=pt(e.nodeName);if(!Ne[t]||ve[t])throw b("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof R)n=_t("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),r.nodeType===J&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!Fe&&!ke&&!ze&&-1===e.indexOf("<"))return le&&We?le.createHTML(e):e;if(n=_t(e),!n)return Fe?null:We?ce:""}n&&He&&Et(n.firstChild);const c=St(Xe?e:n);for(;i=c.nextNode();)wt(i),vt(i),i.content instanceof s&&Lt(i.content);if(Xe)return e;if(Fe){if(Be)for(l=me.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(we.shadowroot||we.shadowrootmode)&&(l=fe.call(a,l,!0)),l}let m=ze?n.outerHTML:n.innerHTML;return ze&&Ne["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&S(K,n.ownerDocument.doctype.name)&&(m="\n"+m),ke&&u([he,ge,Te],(e=>{m=y(m,e," ")})),le&&We?le.createHTML(m):m},o.setConfig=function(){gt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),Pe=!0},o.clearConfig=function(){ft=null,Pe=!1},o.isValidAttribute=function(e,t,n){ft||gt({});const o=pt(e),r=pt(t);return Ot(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&f(de[e],t)},o.removeHook=function(e,t){if(void 0!==t){const n=m(de[e],t);return-1===n?void 0:d(de[e],n,1)[0]}return p(de[e])},o.removeHooks=function(e){de[e]=[]},o.removeAllHooks=function(){de={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},o}();return re}));
+//# sourceMappingURL=purify.min.js.map
diff --git a/public/svgs/bluesky.svg b/public/svgs/bluesky.svg
new file mode 100644
index 000000000..77ebea072
--- /dev/null
+++ b/public/svgs/bluesky.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/svgs/chroma.svg b/public/svgs/chroma.svg
new file mode 100644
index 000000000..930288fbf
--- /dev/null
+++ b/public/svgs/chroma.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/public/svgs/drizzle.jpeg b/public/svgs/drizzle.jpeg
new file mode 100644
index 000000000..d84ff854b
Binary files /dev/null and b/public/svgs/drizzle.jpeg differ
diff --git a/public/svgs/elasticsearch.svg b/public/svgs/elasticsearch.svg
new file mode 100644
index 000000000..bfc5bfb6a
--- /dev/null
+++ b/public/svgs/elasticsearch.svg
@@ -0,0 +1,16 @@
+
diff --git a/public/svgs/github-runner.png b/public/svgs/github-runner.png
new file mode 100644
index 000000000..fb5b5c1b7
Binary files /dev/null and b/public/svgs/github-runner.png differ
diff --git a/public/svgs/gowa.svg b/public/svgs/gowa.svg
new file mode 100644
index 000000000..1121b05bc
--- /dev/null
+++ b/public/svgs/gowa.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/svgs/homebox.svg b/public/svgs/homebox.svg
new file mode 100644
index 000000000..08670bbb9
--- /dev/null
+++ b/public/svgs/homebox.svg
@@ -0,0 +1,11 @@
+
diff --git a/public/svgs/langfuse.png b/public/svgs/langfuse.png
deleted file mode 100644
index 8dec0fe4a..000000000
Binary files a/public/svgs/langfuse.png and /dev/null differ
diff --git a/public/svgs/langfuse.svg b/public/svgs/langfuse.svg
new file mode 100644
index 000000000..b04e07490
--- /dev/null
+++ b/public/svgs/langfuse.svg
@@ -0,0 +1,9 @@
+
diff --git a/public/svgs/librechat.svg b/public/svgs/librechat.svg
new file mode 100644
index 000000000..36a536d65
--- /dev/null
+++ b/public/svgs/librechat.svg
@@ -0,0 +1,32 @@
+
diff --git a/public/svgs/matrix.svg b/public/svgs/matrix.svg
new file mode 100644
index 000000000..bc41720a2
--- /dev/null
+++ b/public/svgs/matrix.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/public/svgs/openpanel.svg b/public/svgs/openpanel.svg
new file mode 100644
index 000000000..8508fc69e
--- /dev/null
+++ b/public/svgs/openpanel.svg
@@ -0,0 +1 @@
+
diff --git a/public/svgs/pihole.svg b/public/svgs/pihole.svg
new file mode 100644
index 000000000..a4efefcc8
--- /dev/null
+++ b/public/svgs/pihole.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/svgs/sequin.svg b/public/svgs/sequin.svg
new file mode 100644
index 000000000..623bc1159
--- /dev/null
+++ b/public/svgs/sequin.svg
@@ -0,0 +1,16 @@
+
diff --git a/public/svgs/triliumnext.svg b/public/svgs/triliumnext.svg
new file mode 100644
index 000000000..173712891
--- /dev/null
+++ b/public/svgs/triliumnext.svg
@@ -0,0 +1,28 @@
+
+
diff --git a/resources/js/terminal.js b/resources/js/terminal.js
index 10535f3ea..b49aad9cf 100644
--- a/resources/js/terminal.js
+++ b/resources/js/terminal.js
@@ -48,6 +48,18 @@ export function initializeTerminalComponent() {
this.sendCommandWhenReady({ command: command });
});
+ this.$wire.on('terminal-should-focus', () => {
+ // Wait for terminal to be ready, then focus
+ const focusWhenReady = () => {
+ if (this.terminalActive && this.term) {
+ this.term.focus();
+ } else {
+ setTimeout(focusWhenReady, 100);
+ }
+ };
+ focusWhenReady();
+ });
+
this.keepAliveInterval = setInterval(this.keepAlive.bind(this), 30000);
this.$watch('terminalActive', (active) => {
@@ -353,6 +365,15 @@ export function initializeTerminalComponent() {
this.resizeTerminal();
}, 200);
+ // Ensure terminal gets focus after connection with multiple attempts
+ setTimeout(() => {
+ this.term.focus();
+ }, 100);
+
+ setTimeout(() => {
+ this.term.focus();
+ }, 500);
+
// Notify parent component that terminal is connected
this.$wire.dispatch('terminalConnected');
} else if (event.data === 'unprocessable') {
diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php
index 249aa18f9..66a924fb8 100644
--- a/resources/views/auth/forgot-password.blade.php
+++ b/resources/views/auth/forgot-password.blade.php
@@ -4,7 +4,7 @@
Coolify
- {{ __('auth.forgot_password') }}
+ {{ __('auth.forgot_password_heading') }}
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php
index 42faf517f..8bd8e81fc 100644
--- a/resources/views/auth/login.blade.php
+++ b/resources/views/auth/login.blade.php
@@ -23,7 +23,7 @@
required label="{{ __('input.password') }}" />
- {{ __('auth.forgot_password') }}?
+ {{ __('auth.forgot_password_link') }}
@else
- {{ __('auth.forgot_password') }}?
+ {{ __('auth.forgot_password_link') }}
@endenv
diff --git a/resources/views/components/applications/links.blade.php b/resources/views/components/applications/links.blade.php
index cf9e9c029..26b1cedf5 100644
--- a/resources/views/components/applications/links.blade.php
+++ b/resources/views/components/applications/links.blade.php
@@ -8,133 +8,85 @@
data_get($application, 'previews', collect([]))->count() > 0 ||
data_get($application, 'ports_mappings_array')) &&
data_get($application, 'settings.is_raw_compose_deployment_enabled') !== true)
- @if (data_get($application, 'gitBrancLocation'))
-
-
- Git Repository
-
- @endif
- @if (data_get($application, 'build_pack') === 'dockercompose')
- @foreach (collect(json_decode($this->application->docker_compose_domains)) as $fqdn)
- @if (data_get($fqdn, 'domain'))
- @foreach (explode(',', data_get($fqdn, 'domain')) as $domain)
-
- {{ getFqdnWithoutPort($domain) }}
-
- @endforeach
- @endif
- @endforeach
- @endif
- @if (data_get($application, 'fqdn'))
- @foreach (str(data_get($application, 'fqdn'))->explode(',') as $fqdn)
-
- {{ getFqdnWithoutPort($fqdn) }}
+
+ @if (data_get($application, 'gitBrancLocation'))
+
+
+ Git Repository
- @endforeach
- @endif
- @if (data_get($application, 'previews', collect())->count() > 0)
- @if (data_get($application, 'build_pack') === 'dockercompose')
- @foreach ($application->previews as $preview)
- @foreach (collect(json_decode($preview->docker_compose_domains)) as $fqdn)
- @if (data_get($fqdn, 'domain'))
- @foreach (explode(',', data_get($fqdn, 'domain')) as $domain)
-
- PR{{ data_get($preview, 'pull_request_id') }} |
- {{ getFqdnWithoutPort($domain) }}
-
- @endforeach
- @endif
- @endforeach
- @endforeach
- @else
- @foreach (data_get($application, 'previews') as $preview)
- @if (data_get($preview, 'fqdn'))
-
-
- PR{{ data_get($preview, 'pull_request_id') }} |
- {{ data_get($preview, 'fqdn') }}
-
- @endif
- @endforeach
@endif
- @endif
- @if (data_get($application, 'ports_mappings_array'))
- @foreach ($application->ports_mappings_array as $port)
- @if ($application->destination->server->id === 0)
-
-
- Port {{ $port }}
-
- @else
-
-
- {{ $application->destination->server->ip }}:{{ explode(':', $port)[0] }}
-
- @if (count($application->additional_servers) > 0)
- @foreach ($application->additional_servers as $server)
-
-
- {{ $server->ip }}:{{ explode(':', $port)[0] }}
+ @if (data_get($application, 'build_pack') === 'dockercompose')
+ @foreach (collect(json_decode($this->application->docker_compose_domains)) as $fqdn)
+ @if (data_get($fqdn, 'domain'))
+ @foreach (explode(',', data_get($fqdn, 'domain')) as $domain)
+
+ {{ getFqdnWithoutPort($domain) }}
@endforeach
@endif
+ @endforeach
+ @endif
+ @if (data_get($application, 'fqdn'))
+ @foreach (str(data_get($application, 'fqdn'))->explode(',') as $fqdn)
+
+ {{ getFqdnWithoutPort($fqdn) }}
+
+ @endforeach
+ @endif
+ @if (data_get($application, 'previews', collect())->count() > 0)
+ @if (data_get($application, 'build_pack') === 'dockercompose')
+ @foreach ($application->previews as $preview)
+ @foreach (collect(json_decode($preview->docker_compose_domains)) as $fqdn)
+ @if (data_get($fqdn, 'domain'))
+ @foreach (explode(',', data_get($fqdn, 'domain')) as $domain)
+
+ PR{{ data_get($preview, 'pull_request_id') }}
+ |
+ {{ getFqdnWithoutPort($domain) }}
+
+ @endforeach
+ @endif
+ @endforeach
+ @endforeach
+ @else
+ @foreach (data_get($application, 'previews') as $preview)
+ @if (data_get($preview, 'fqdn'))
+
+
+ PR{{ data_get($preview, 'pull_request_id') }} |
+ {{ data_get($preview, 'fqdn') }}
+
+ @endif
+ @endforeach
@endif
- @endforeach
- @endif
+ @endif
+ @if (data_get($application, 'ports_mappings_array'))
+ @foreach ($application->ports_mappings_array as $port)
+ @if ($application->destination->server->id === 0)
+
+
+ Port {{ $port }}
+
+ @else
+
+
+ {{ $application->destination->server->ip }}:{{ explode(':', $port)[0] }}
+
+ @if (count($application->additional_servers) > 0)
+ @foreach ($application->additional_servers as $server)
+
+
+ {{ $server->ip }}:{{ explode(':', $port)[0] }}
+
+ @endforeach
+ @endif
+ @endif
+ @endforeach
+ @endif
+
@else
No links available
@endif
diff --git a/resources/views/components/external-link.blade.php b/resources/views/components/external-link.blade.php
index ddf03427f..9e68704cd 100644
--- a/resources/views/components/external-link.blade.php
+++ b/resources/views/components/external-link.blade.php
@@ -1,7 +1,6 @@
-
@else
@@ -45,7 +46,8 @@
max="{{ $attributes->get('max') }}" minlength="{{ $attributes->get('minlength') }}"
maxlength="{{ $attributes->get('maxlength') }}"
@if ($id !== 'null') id={{ $id }} @endif name="{{ $name }}"
- placeholder="{{ $attributes->get('placeholder') }}">
+ placeholder="{{ $attributes->get('placeholder') }}"
+ @if ($autofocus) x-ref="autofocusInput" @endif>
@endif
@if (!$label && $helper)
diff --git a/resources/views/components/modal-confirmation.blade.php b/resources/views/components/modal-confirmation.blade.php
index f5a0ca84a..0d185782f 100644
--- a/resources/views/components/modal-confirmation.blade.php
+++ b/resources/views/components/modal-confirmation.blade.php
@@ -42,7 +42,11 @@
deleteText: '',
password: '',
actions: @js($actions),
- confirmationText: @js(html_entity_decode($confirmationText, ENT_QUOTES, 'UTF-8')),
+ confirmationText: (() => {
+ const textarea = document.createElement('textarea');
+ textarea.innerHTML = @js($confirmationText);
+ return textarea.value;
+ })(),
userConfirmationText: '',
confirmWithText: @js($confirmWithText && !$disableTwoStepConfirmation),
confirmWithPassword: @js($confirmWithPassword && !$disableTwoStepConfirmation),
@@ -257,8 +261,21 @@
Confirm Actions
{{ $confirmationLabel }}
-
-
+
+
+
+
+
-
+
+You have requested to change your email address to: {{ $newEmail }}
+
+Please use the following verification code to confirm this change:
+
+Verification Code: {{ $verificationCode }}
+
+This code is valid for {{ $expiryMinutes }} minutes.
+
+If you did not request this change, please ignore this email and your email address will remain unchanged.
+
\ No newline at end of file
diff --git a/resources/views/errors/400.blade.php b/resources/views/errors/400.blade.php
index 2276f5a62..4b5956142 100644
--- a/resources/views/errors/400.blade.php
+++ b/resources/views/errors/400.blade.php
@@ -6,13 +6,17 @@
@if ($exception->getMessage())
{{ $exception->getMessage() }}
@else
- The request could not be understood by the server due to
+
The request could not be understood by the
+ server due to
malformed syntax.
@endif
-
-
- Go back home
+
+
+ Go back
+
+
+ Dashboard
Contact
support
diff --git a/resources/views/errors/401.blade.php b/resources/views/errors/401.blade.php
index e0a44aed8..95449c141 100644
--- a/resources/views/errors/401.blade.php
+++ b/resources/views/errors/401.blade.php
@@ -3,11 +3,14 @@
401
You shall not pass!
- You don't have permission to access this page.
+
You don't have permission to access this page.
-
-
- Go back home
+
+
+ Go back
+
+
+ Dashboard
Contact
support
diff --git a/resources/views/errors/402.blade.php b/resources/views/errors/402.blade.php
index 9758dec2d..6534615df 100644
--- a/resources/views/errors/402.blade.php
+++ b/resources/views/errors/402.blade.php
@@ -4,9 +4,12 @@
402
Payment required.
-
-
- Go back home
+
+
+ Go back
+
+
+ Dashboard
Contact
support
diff --git a/resources/views/errors/403.blade.php b/resources/views/errors/403.blade.php
index f54a2866a..50317700d 100644
--- a/resources/views/errors/403.blade.php
+++ b/resources/views/errors/403.blade.php
@@ -3,11 +3,14 @@
403
You shall not pass!
- You don't have permission to access this page.
+
You don't have permission to access this page.
-
-
- Go back home
+
+
+ Go back
+
+
+ Dashboard
Contact
support
diff --git a/resources/views/errors/404.blade.php b/resources/views/errors/404.blade.php
index 569488d19..67fb0f0f1 100644
--- a/resources/views/errors/404.blade.php
+++ b/resources/views/errors/404.blade.php
@@ -3,12 +3,15 @@
404
How did you get here?
- Sorry, we couldn’t find the page you’re looking
+
Sorry, we couldn’t find the page you’re looking
for.
-
-
- Go back home
+
+
+ Go back
+
+
+ Dashboard
Contact
support
diff --git a/resources/views/errors/419.blade.php b/resources/views/errors/419.blade.php
index 723ba9f55..5367898f0 100644
--- a/resources/views/errors/419.blade.php
+++ b/resources/views/errors/419.blade.php
@@ -3,12 +3,15 @@
419
This page is definitely old, not like you!
- Sorry, we couldn’t find the page you’re looking
+
Sorry, we couldn’t find the page you’re looking
for.
-
-
- Go back home
+
+
+ Go back
+
+
+ Dashboard
Contact
support
diff --git a/resources/views/errors/429.blade.php b/resources/views/errors/429.blade.php
index 443244351..36c8e95f6 100644
--- a/resources/views/errors/429.blade.php
+++ b/resources/views/errors/429.blade.php
@@ -3,12 +3,16 @@
429
Woah, slow down there!
- You're making too many requests. Please wait a few
+
You're making too many requests. Please wait a
+ few
seconds before trying again.
-
-
- Go back home
+
+
+ Go back
+
+
+ Dashboard
Contact
support
diff --git a/resources/views/errors/500.blade.php b/resources/views/errors/500.blade.php
index cc672a324..149be2685 100644
--- a/resources/views/errors/500.blade.php
+++ b/resources/views/errors/500.blade.php
@@ -3,18 +3,22 @@
500
Wait, this is not cool...
- There has been an error with the following error message:
+ There has been an error with the following
+ error message:
@if ($exception->getMessage() !== '')
{!! Purify::clean($exception->getMessage()) !!}
@endif
-
-
- Go back home
+
diff --git a/resources/views/errors/503.blade.php b/resources/views/errors/503.blade.php
index 668ea5e3a..7db859624 100644
--- a/resources/views/errors/503.blade.php
+++ b/resources/views/errors/503.blade.php
@@ -3,10 +3,17 @@
503
We are working on serious things.
- Service Unavailable. Be right back. Thanks for your
+
Service Unavailable. Be right back. Thanks for
+ your
patience.
-
+
+
+ Go back
+
+
+ Dashboard
+
Contact
support
diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php
index d9975c975..ebb134324 100644
--- a/resources/views/layouts/base.blade.php
+++ b/resources/views/layouts/base.blade.php
@@ -35,9 +35,9 @@
@endphp
{{ $name }}{{ $title ?? 'Coolify' }}
@env('local')
-
-@else
-
+
+ @else
+
@endenv
@vite(['resources/js/app.js', 'resources/css/app.css'])
@@ -54,6 +54,7 @@
+
@endauth
@section('body')
@@ -61,6 +62,67 @@