mirror of
https://github.com/coollabsio/coolify.git
synced 2026-09-26 01:10:30 -04:00
Merge remote-tracking branch 'origin/main' into next
This commit is contained in:
@@ -86,7 +86,7 @@ class Advanced extends Component
|
||||
public bool $isConnectToDockerNetworkEnabled = false;
|
||||
|
||||
#[Validate(['integer', 'min:0'])]
|
||||
public int $maxRestartCount = 10;
|
||||
public int $maxRestartCount = 0;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@@ -148,7 +148,7 @@ class Advanced extends Component
|
||||
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||
$this->injectBuildArgsToDockerfile = $this->application->settings->inject_build_args_to_dockerfile ?? true;
|
||||
$this->includeSourceCommitInBuild = $this->application->settings->include_source_commit_in_build ?? false;
|
||||
$this->maxRestartCount = $this->application->max_restart_count ?? 10;
|
||||
$this->maxRestartCount = $this->application->max_restart_count ?? 0;
|
||||
}
|
||||
|
||||
// Load stop_grace_period separately since it has its own save handler
|
||||
|
||||
@@ -84,7 +84,7 @@ class Index extends Component
|
||||
|
||||
public bool $isStripprefixEnabled = false;
|
||||
|
||||
public mixed $maxRestartCount = 10;
|
||||
public mixed $maxRestartCount = 0;
|
||||
|
||||
protected $listeners = ['generateDockerCompose', 'refreshScheduledBackups' => '$refresh', 'refreshFileStorages'];
|
||||
|
||||
@@ -432,7 +432,7 @@ class Index extends Component
|
||||
$this->isLogDrainEnabled = data_get($this->serviceApplication, 'is_log_drain_enabled', false);
|
||||
$this->isGzipEnabled = data_get($this->serviceApplication, 'is_gzip_enabled', true);
|
||||
$this->isStripprefixEnabled = data_get($this->serviceApplication, 'is_stripprefix_enabled', true);
|
||||
$this->maxRestartCount = $this->serviceApplication->max_restart_count ?? 10;
|
||||
$this->maxRestartCount = $this->serviceApplication->max_restart_count ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,6 +136,10 @@ class Application extends BaseModel
|
||||
|
||||
private static $parserVersion = '5';
|
||||
|
||||
protected $attributes = [
|
||||
'max_restart_count' => 0,
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
|
||||
@@ -13,6 +13,10 @@ class ApplicationPreview extends BaseModel
|
||||
{
|
||||
use HasRestartLimit, SoftDeletes;
|
||||
|
||||
protected $attributes = [
|
||||
'max_restart_count' => 0,
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'application_id',
|
||||
|
||||
@@ -54,6 +54,7 @@ class ServiceApplication extends BaseModel
|
||||
|
||||
protected $attributes = [
|
||||
'is_force_https_enabled' => true,
|
||||
'max_restart_count' => 0,
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
|
||||
@@ -2400,7 +2400,7 @@ function get_public_ips()
|
||||
}
|
||||
}
|
||||
|
||||
function isAnyDeploymentInprogress()
|
||||
function isAnyDeploymentInprogress(bool $showAll = false)
|
||||
{
|
||||
$runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)->get();
|
||||
|
||||
@@ -2417,34 +2417,31 @@ function isAnyDeploymentInprogress()
|
||||
if ($horizonJobStatus === 'unknown' || $horizonJobStatus === 'reserved') {
|
||||
$horizonJobIds[] = $runningJob->horizon_job_id;
|
||||
|
||||
// Get application and team information
|
||||
$application = Application::find($runningJob->application_id);
|
||||
$teamMembers = [];
|
||||
$deploymentUrl = '';
|
||||
if ($showAll) {
|
||||
$application = Application::find($runningJob->application_id);
|
||||
$teamMembers = [];
|
||||
$deploymentUrl = '';
|
||||
|
||||
if ($application) {
|
||||
// Get team members through the application's project
|
||||
$team = $application->team();
|
||||
if ($team) {
|
||||
$teamMembers = $team->members()->pluck('email')->toArray();
|
||||
if ($application) {
|
||||
$team = $application->team();
|
||||
if ($team) {
|
||||
$teamMembers = $team->members()->pluck('email')->toArray();
|
||||
}
|
||||
|
||||
if ($runningJob->deployment_url) {
|
||||
$deploymentUrl = base_url().$runningJob->deployment_url;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the full deployment URL
|
||||
if ($runningJob->deployment_url) {
|
||||
$baseUrl = base_url();
|
||||
$deploymentUrl = $baseUrl.$runningJob->deployment_url;
|
||||
}
|
||||
$deploymentDetails[] = [
|
||||
'application_name' => $runningJob->application_name ?? 'Unknown',
|
||||
'server_name' => $runningJob->server_name ?? 'Unknown',
|
||||
'deployment_url' => $deploymentUrl,
|
||||
'team_members' => $teamMembers,
|
||||
'created_at' => $runningJob->created_at->format('Y-m-d H:i:s'),
|
||||
'horizon_job_id' => $runningJob->horizon_job_id,
|
||||
];
|
||||
}
|
||||
|
||||
$deploymentDetails[] = [
|
||||
'id' => $runningJob->id,
|
||||
'application_name' => $runningJob->application_name ?? 'Unknown',
|
||||
'server_name' => $runningJob->server_name ?? 'Unknown',
|
||||
'deployment_url' => $deploymentUrl,
|
||||
'team_members' => $teamMembers,
|
||||
'created_at' => $runningJob->created_at->format('Y-m-d H:i:s'),
|
||||
'horizon_job_id' => $runningJob->horizon_job_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2453,30 +2450,41 @@ function isAnyDeploymentInprogress()
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Display enhanced deployment information
|
||||
echo "\n=== Running Deployments ===\n";
|
||||
echo 'Total active deployments: '.count($horizonJobIds)."\n\n";
|
||||
|
||||
foreach ($deploymentDetails as $index => $deployment) {
|
||||
echo 'Deployment #'.($index + 1).":\n";
|
||||
echo ' Application: '.$deployment['application_name']."\n";
|
||||
echo ' Server: '.$deployment['server_name']."\n";
|
||||
echo ' Started: '.$deployment['created_at']."\n";
|
||||
if ($deployment['deployment_url']) {
|
||||
echo ' URL: '.$deployment['deployment_url']."\n";
|
||||
}
|
||||
if (! empty($deployment['team_members'])) {
|
||||
echo ' Team members: '.implode(', ', $deployment['team_members'])."\n";
|
||||
} else {
|
||||
echo " Team members: No team members found\n";
|
||||
}
|
||||
echo ' Horizon Job ID: '.$deployment['horizon_job_id']."\n";
|
||||
echo "\n";
|
||||
}
|
||||
echo formatRunningDeploymentsOutput(count($horizonJobIds), $deploymentDetails, $showAll);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
function formatRunningDeploymentsOutput(int $activeDeploymentCount, array $deploymentDetails = [], bool $showAll = false): string
|
||||
{
|
||||
$output = "\n=== Running Deployments ===\n";
|
||||
$output .= 'Total active deployments: '.$activeDeploymentCount."\n";
|
||||
|
||||
if (! $showAll) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
$output .= "\n";
|
||||
|
||||
foreach ($deploymentDetails as $index => $deployment) {
|
||||
$output .= 'Deployment #'.($index + 1).":\n";
|
||||
$output .= ' Application: '.$deployment['application_name']."\n";
|
||||
$output .= ' Server: '.$deployment['server_name']."\n";
|
||||
$output .= ' Started: '.$deployment['created_at']."\n";
|
||||
if ($deployment['deployment_url']) {
|
||||
$output .= ' URL: '.$deployment['deployment_url']."\n";
|
||||
}
|
||||
if (! empty($deployment['team_members'])) {
|
||||
$output .= ' Team members: '.implode(', ', $deployment['team_members'])."\n";
|
||||
} else {
|
||||
$output .= " Team members: No team members found\n";
|
||||
}
|
||||
$output .= ' Horizon Job ID: '.$deployment['horizon_job_id']."\n\n";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function isBase64Encoded($strValue)
|
||||
{
|
||||
return base64_encode(base64_decode($strValue, true)) === $strValue;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
return [
|
||||
'coolify' => [
|
||||
'version' => env('COOLIFY_VERSION') ?: '4.3.20',
|
||||
'version' => env('COOLIFY_VERSION') ?: '4.3.21',
|
||||
'helper_version' => '1.0.17',
|
||||
'realtime_version' => '1.0.19',
|
||||
'railpack_version' => '0.23.0',
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public $withinTransaction = false;
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
foreach (['applications', 'application_previews', 'service_applications'] as $tableName) {
|
||||
Schema::table($tableName, function (Blueprint $table) {
|
||||
$table->integer('max_restart_count')->default(0)->change();
|
||||
});
|
||||
|
||||
DB::table($tableName)
|
||||
->select('id')
|
||||
->where('max_restart_count', 10)
|
||||
->chunkById(5000, function ($resources) use ($tableName): void {
|
||||
DB::table($tableName)
|
||||
->whereIn('id', $resources->pluck('id'))
|
||||
->update([
|
||||
'max_restart_count' => 0,
|
||||
'restart_limit_reached' => false,
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
foreach (['applications', 'application_previews', 'service_applications'] as $tableName) {
|
||||
Schema::table($tableName, function (Blueprint $table) {
|
||||
$table->integer('max_restart_count')->default(10)->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -162,7 +162,7 @@ services:
|
||||
networks:
|
||||
- coolify
|
||||
# maxio-init:
|
||||
# image: quay.io/minio/aistor/mc:RELEASE.2026-09-06T02-44-40Z
|
||||
# image: ghcr.io/coollabsio/mx:0.1.0
|
||||
# pull_policy: always
|
||||
# container_name: coolify-maxio-init
|
||||
# restart: no
|
||||
@@ -182,7 +182,7 @@ services:
|
||||
# networks:
|
||||
# - coolify
|
||||
minio-init:
|
||||
image: quay.io/minio/aistor/mc:RELEASE.2026-09-06T02-44-40Z
|
||||
image: ghcr.io/coollabsio/mx:0.1.0
|
||||
pull_policy: always
|
||||
container_name: coolify-minio-init
|
||||
restart: no
|
||||
|
||||
@@ -195,7 +195,7 @@ services:
|
||||
|
||||
minio-init:
|
||||
profiles: ["minio"]
|
||||
image: quay.io/minio/aistor/mc:RELEASE.2026-09-06T02-44-40Z
|
||||
image: ghcr.io/coollabsio/mx:0.1.0
|
||||
pull_policy: always
|
||||
restart: "no"
|
||||
depends_on:
|
||||
|
||||
@@ -150,7 +150,7 @@ services:
|
||||
networks:
|
||||
- coolify
|
||||
minio-init:
|
||||
image: quay.io/minio/aistor/mc:RELEASE.2026-09-06T02-44-40Z
|
||||
image: ghcr.io/coollabsio/mx:0.1.0
|
||||
pull_policy: always
|
||||
container_name: coolify-minio-init
|
||||
restart: no
|
||||
|
||||
@@ -12,7 +12,7 @@ ARG NGINX_VERSION=1.31.2-r1
|
||||
# =================================================================
|
||||
# Get MinIO client
|
||||
# =================================================================
|
||||
FROM quay.io/minio/aistor/mc:RELEASE.2026-09-06T02-44-40Z AS minio-client
|
||||
FROM ghcr.io/coollabsio/mx:0.1.0 AS minio-client
|
||||
|
||||
# =================================================================
|
||||
# Final Stage: Production image
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"coolify": {
|
||||
"v4": {
|
||||
"version": "4.3.20"
|
||||
"version": "4.3.21"
|
||||
},
|
||||
"nightly": {
|
||||
"version": "4.4-rc.1"
|
||||
@@ -17,14 +17,14 @@
|
||||
}
|
||||
},
|
||||
"traefik": {
|
||||
"v3.7": "3.7.8",
|
||||
"v3.6": "3.6.23",
|
||||
"v3.7": "3.7.13",
|
||||
"v3.6": "3.6.25",
|
||||
"v3.5": "3.5.6",
|
||||
"v3.4": "3.4.5",
|
||||
"v3.3": "3.3.7",
|
||||
"v3.2": "3.2.5",
|
||||
"v3.1": "3.1.7",
|
||||
"v3.0": "3.0.4",
|
||||
"v2.11": "2.11.52"
|
||||
"v2.11": "2.11.57"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
/* input, select before */
|
||||
@utility input-select {
|
||||
@apply block h-9 px-3 py-1.5 w-full text-sm text-black rounded-md border border-neutral-200 bg-[var(--coollabs-recessed)] dark:text-fg dark:border-white/[0.08] transition-colors disabled:bg-neutral-100 disabled:text-neutral-400 dark:disabled:bg-white/[0.03] dark:disabled:text-fg-faint;
|
||||
@apply block h-8 px-3 py-1.5 w-full text-sm text-black rounded-md border border-neutral-200 bg-[var(--coollabs-recessed)] dark:text-fg dark:border-white/[0.08] transition-colors disabled:bg-neutral-100 disabled:text-neutral-400 dark:disabled:bg-white/[0.03] dark:disabled:text-fg-faint;
|
||||
box-shadow: none;
|
||||
|
||||
&:where(.dark, .dark *) {
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
{{ $project->name }}
|
||||
</h3>
|
||||
<p class="mt-0.5 truncate text-[11px] text-neutral-500 dark:text-fg-faint">
|
||||
{{ $project->description ?: 'No description' }}
|
||||
{{ $project->description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -186,7 +186,7 @@
|
||||
{{ $server->name }}
|
||||
</h3>
|
||||
<p class="mt-0.5 truncate text-[11px] text-neutral-500 dark:text-fg-faint">
|
||||
{{ $server->description ?: 'No description' }}
|
||||
{{ $server->description }}
|
||||
</p>
|
||||
</div>
|
||||
@if ($serverStatusType !== 'success')
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
class="truncate text-[13px]! leading-4! font-semibold! text-black dark:text-fg"
|
||||
x-text="project.name"></h2>
|
||||
<p class="mt-0.5 truncate text-[11px] text-neutral-500 dark:text-fg-faint"
|
||||
x-text="project.description || 'No description'"></p>
|
||||
x-text="project.description || ''"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
return [
|
||||
'uuid' => $server->uuid,
|
||||
'name' => $server->name,
|
||||
'description' => $server->description ?: 'No description',
|
||||
'description' => $server->description,
|
||||
'href' => route('server.show', ['server_uuid' => $server->uuid]),
|
||||
'status' => $status,
|
||||
'statusType' => $statusType,
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
@endif
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-fg-dim">
|
||||
{{ $privateKey->description ?: 'No description provided.' }}
|
||||
{{ $privateKey->description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
href="{{ route('shared-variables.environment.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid]) }}" {{ wireNavigate() }}>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex size-8 shrink-0 items-center justify-center rounded-lg border border-neutral-200 bg-neutral-50 text-neutral-500 dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-fg-dim"><x-reicon name="layers" class="size-4" /></div>
|
||||
<div class="min-w-0 flex-1"><h3 class="truncate text-[13px]! leading-4! font-semibold! text-black dark:text-fg">{{ $environment->name }}</h3><p class="mt-0.5 truncate text-[11px] text-neutral-500 dark:text-fg-faint">{{ $environment->description ?: 'No description' }}</p></div>
|
||||
<div class="min-w-0 flex-1"><h3 class="truncate text-[13px]! leading-4! font-semibold! text-black dark:text-fg">{{ $environment->name }}</h3><p class="mt-0.5 truncate text-[11px] text-neutral-500 dark:text-fg-faint">{{ $environment->description }}</p></div>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
@@ -50,7 +50,7 @@
|
||||
href="{{ route('shared-variables.environment.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid]) }}" {{ wireNavigate() }}
|
||||
class="flex min-h-14 items-center gap-3 border-b border-neutral-200 px-4 py-2.5 last:border-b-0 hover:bg-neutral-50 hover:no-underline dark:border-white/[0.07] dark:hover:bg-white/[0.025]">
|
||||
<x-reicon name="layers" class="size-4 shrink-0 text-neutral-500 dark:text-fg-dim" />
|
||||
<div class="min-w-0 flex-1"><div class="truncate text-[13px] font-medium">{{ $environment->name }}</div><div class="truncate text-[11px] text-neutral-500 dark:text-fg-faint">{{ $environment->description ?: 'No description' }}</div></div>
|
||||
<div class="min-w-0 flex-1"><div class="truncate text-[13px] font-medium">{{ $environment->name }}</div><div class="truncate text-[11px] text-neutral-500 dark:text-fg-faint">{{ $environment->description }}</div></div>
|
||||
<span class="shrink-0 text-[11px] text-neutral-500 dark:text-fg-dim">{{ $project->name }}</span>
|
||||
</a>
|
||||
@endforeach
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<h2 class="truncate text-[13px]! leading-4! font-semibold! text-black dark:text-fg">{{ $project->name }}</h2>
|
||||
<p class="mt-0.5 truncate text-[11px] text-neutral-500 dark:text-fg-faint">{{ $project->description ?: 'No description' }}</p>
|
||||
<p class="mt-0.5 truncate text-[11px] text-neutral-500 dark:text-fg-faint">{{ $project->description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-auto pt-4 text-[11px] text-neutral-500 dark:text-fg-dim">
|
||||
@@ -54,7 +54,7 @@
|
||||
@else
|
||||
<x-reicon name="projects" class="size-4 shrink-0 text-neutral-500 dark:text-fg-dim" />
|
||||
@endif
|
||||
<div class="min-w-0 flex-1"><div class="truncate text-[13px] font-medium">{{ $project->name }}</div><div class="truncate text-[11px] text-neutral-500 dark:text-fg-faint">{{ $project->description ?: 'No description' }}</div></div>
|
||||
<div class="min-w-0 flex-1"><div class="truncate text-[13px] font-medium">{{ $project->name }}</div><div class="truncate text-[11px] text-neutral-500 dark:text-fg-faint">{{ $project->description }}</div></div>
|
||||
<span class="shrink-0 text-[11px] text-neutral-500 dark:text-fg-dim">{{ $project->environment_variables()->count() }} {{ Str::plural('variable', $project->environment_variables()->count()) }}</span>
|
||||
</a>
|
||||
@endforeach
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Actions\Service\StopServiceApplication;
|
||||
use App\Jobs\PushServerUpdateJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
@@ -209,6 +210,38 @@ it('limits restarts only for applications', function () {
|
||||
->toContain('$resetRestartCount && $serviceApplication instanceof ServiceApplication');
|
||||
});
|
||||
|
||||
it('makes restart limits opt in for new application resources', function () {
|
||||
$migrationPaths = glob(database_path('migrations/*_make_restart_limits_opt_in.php'));
|
||||
|
||||
expect($migrationPaths)->toHaveCount(1);
|
||||
|
||||
$migration = file_get_contents($migrationPaths[0]);
|
||||
|
||||
expect($migration)
|
||||
->toContain("['applications', 'application_previews', 'service_applications']")
|
||||
->toContain("integer('max_restart_count')->default(0)->change()")
|
||||
->toContain('public $withinTransaction = false;')
|
||||
->toContain("->where('max_restart_count', 10)")
|
||||
->toContain('->chunkById(5000')
|
||||
->toContain("->whereIn('id', \$resources->pluck('id'))")
|
||||
->toContain("'max_restart_count' => 0")
|
||||
->toContain("'restart_limit_reached' => false");
|
||||
|
||||
$applicationSettings = file_get_contents(app_path('Livewire/Project/Application/Advanced.php'));
|
||||
$serviceSettings = file_get_contents(app_path('Livewire/Project/Service/Index.php'));
|
||||
|
||||
expect($applicationSettings)
|
||||
->toContain('public int $maxRestartCount = 0;')
|
||||
->toContain('$this->application->max_restart_count ?? 0')
|
||||
->and($serviceSettings)
|
||||
->toContain('public mixed $maxRestartCount = 0;')
|
||||
->toContain('$this->serviceApplication->max_restart_count ?? 0');
|
||||
|
||||
foreach ([Application::class, ApplicationPreview::class, ServiceApplication::class] as $modelClass) {
|
||||
expect((new $modelClass)->max_restart_count)->toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not render restart limit warnings for service databases', function () {
|
||||
$html = Blade::render(
|
||||
'<x-application.restart-limit-warning :application="$database" />',
|
||||
|
||||
@@ -11,12 +11,13 @@ test('default form buttons and inputs share the same control height', function (
|
||||
preg_match('/@utility button \{[^}]*\}/s', $utilities, $button);
|
||||
|
||||
expect($inputSelect[0] ?? '')
|
||||
->toContain('h-9')
|
||||
->and($button[0] ?? '')->toContain('h-9')
|
||||
->and($button[0] ?? '')->toContain('min-h-9')
|
||||
->toContain('h-8')
|
||||
->not->toContain('h-9')
|
||||
->and($button[0] ?? '')->toContain('h-8')
|
||||
->and($button[0] ?? '')->toContain('min-h-8')
|
||||
->and($button[0] ?? '')->toContain('whitespace-nowrap')
|
||||
->and($button[0] ?? '')->toContain('shrink-0')
|
||||
->and($button[0] ?? '')->not->toContain('h-8');
|
||||
->and($button[0] ?? '')->not->toContain('h-9');
|
||||
});
|
||||
|
||||
test('settings form surfaces keep input and button heights equal', function () {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
it('does not show placeholder copy when a description is empty', function () {
|
||||
$views = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator(__DIR__.'/../../resources/views')
|
||||
);
|
||||
$viewContents = '';
|
||||
|
||||
foreach ($views as $view) {
|
||||
if ($view->isFile() && $view->getExtension() === 'php') {
|
||||
$viewContents .= file_get_contents($view->getPathname());
|
||||
}
|
||||
}
|
||||
|
||||
expect($viewContents)->not->toContain('No description');
|
||||
});
|
||||
@@ -1,21 +1,27 @@
|
||||
<?php
|
||||
|
||||
it('pins the AIStor MinIO client release in every Coolify image', function () {
|
||||
$imageFiles = [
|
||||
dirname(__DIR__, 2).'/docker/production/Dockerfile',
|
||||
it('uses the mx MinIO-compatible client in development', function () {
|
||||
$developmentFiles = [
|
||||
dirname(__DIR__, 2).'/docker/development/Dockerfile',
|
||||
dirname(__DIR__, 2).'/docker/coolify-helper/Dockerfile',
|
||||
dirname(__DIR__, 2).'/docker-compose.dev.yml',
|
||||
dirname(__DIR__, 2).'/docker-compose.dev-multi.yml',
|
||||
dirname(__DIR__, 2).'/docker-compose-maxio.dev.yml',
|
||||
];
|
||||
|
||||
foreach ($imageFiles as $imageFile) {
|
||||
$contents = file_get_contents($imageFile);
|
||||
foreach ($developmentFiles as $developmentFile) {
|
||||
$contents = file_get_contents($developmentFile);
|
||||
|
||||
expect($contents)
|
||||
->toContain('quay.io/minio/aistor/mc:RELEASE.2026-09-06T02-44-40Z')
|
||||
->not->toContain('quay.io/minio/aistor/mc:latest')
|
||||
->toContain('ghcr.io/coollabsio/mx:0.1.0')
|
||||
->not->toContain('quay.io/minio/aistor/mc:')
|
||||
->not->toContain('minio/mc:');
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps the mc command path when packaging mx', function () {
|
||||
$dockerfile = file_get_contents(dirname(__DIR__, 2).'/docker/development/Dockerfile');
|
||||
|
||||
expect($dockerfile)
|
||||
->toContain('COPY --from=minio-client /usr/bin/mc /usr/bin/mc')
|
||||
->not->toContain('COPY --from=minio-client /usr/bin/mx');
|
||||
});
|
||||
|
||||
@@ -23,8 +23,8 @@ it('publishes v4 branch builds under the commit sha with a traceable internal ve
|
||||
->toContain('ARG COOLIFY_VERSION')
|
||||
->toContain('ENV COOLIFY_VERSION=${COOLIFY_VERSION}')
|
||||
->and($constants)
|
||||
->toContain("'version' => env('COOLIFY_VERSION') ?: '4.3.19'")
|
||||
->and($versions['coolify']['v4']['version'])->toBe('4.3.19')
|
||||
->toContain("'version' => env('COOLIFY_VERSION') ?: '4.3.21'")
|
||||
->and($versions['coolify']['v4']['version'])->toBe('4.3.21')
|
||||
->and($versions['coolify']['nightly']['version'])->toBe('4.4-rc.1')
|
||||
->and($nightlyVersions)->toBe($versions);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
it('shows only the active deployment count by default', function () {
|
||||
$output = formatRunningDeploymentsOutput(1, [[
|
||||
'application_name' => 'secret-application',
|
||||
'server_name' => 'secret-server',
|
||||
'deployment_url' => 'https://example.com/secret-deployment',
|
||||
'team_members' => ['member@example.com'],
|
||||
'created_at' => '2026-09-15 08:52:42',
|
||||
'horizon_job_id' => 'secret-job-id',
|
||||
]]);
|
||||
|
||||
expect($output)
|
||||
->toBe("\n=== Running Deployments ===\nTotal active deployments: 1\n")
|
||||
->not->toContain('secret-application')
|
||||
->not->toContain('https://example.com/secret-deployment')
|
||||
->not->toContain('member@example.com');
|
||||
});
|
||||
|
||||
it('shows all deployment details when requested', function () {
|
||||
$output = formatRunningDeploymentsOutput(1, [[
|
||||
'application_name' => 'example-application',
|
||||
'server_name' => 'example-server',
|
||||
'deployment_url' => 'https://example.com/deployment',
|
||||
'team_members' => ['member@example.com'],
|
||||
'created_at' => '2026-09-15 08:52:42',
|
||||
'horizon_job_id' => 'example-job-id',
|
||||
]], true);
|
||||
|
||||
expect($output)
|
||||
->toContain('Deployment #1:')
|
||||
->toContain('Application: example-application')
|
||||
->toContain('Server: example-server')
|
||||
->toContain('Started: 2026-09-15 08:52:42')
|
||||
->toContain('URL: https://example.com/deployment')
|
||||
->toContain('Team members: member@example.com')
|
||||
->toContain('Horizon Job ID: example-job-id');
|
||||
});
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"coolify": {
|
||||
"v4": {
|
||||
"version": "4.3.20"
|
||||
"version": "4.3.21"
|
||||
},
|
||||
"nightly": {
|
||||
"version": "4.4-rc.1"
|
||||
@@ -17,14 +17,14 @@
|
||||
}
|
||||
},
|
||||
"traefik": {
|
||||
"v3.7": "3.7.8",
|
||||
"v3.6": "3.6.23",
|
||||
"v3.7": "3.7.13",
|
||||
"v3.6": "3.6.25",
|
||||
"v3.5": "3.5.6",
|
||||
"v3.4": "3.4.5",
|
||||
"v3.3": "3.3.7",
|
||||
"v3.2": "3.2.5",
|
||||
"v3.1": "3.1.7",
|
||||
"v3.0": "3.0.4",
|
||||
"v2.11": "2.11.52"
|
||||
"v2.11": "2.11.57"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user