Skip to content

Commit a2fa98d

Browse files
authored
v4.0.0-beta.462 (#7967)
2 parents 04e7191 + b971440 commit a2fa98d

File tree

12 files changed

+208
-40
lines changed

12 files changed

+208
-40
lines changed

app/Livewire/Project/Resource/Index.php

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ class Index extends Component
3333

3434
public Collection $services;
3535

36+
public Collection $allProjects;
37+
38+
public Collection $allEnvironments;
39+
3640
public array $parameters;
3741

3842
public function mount()
@@ -50,6 +54,33 @@ public function mount()
5054
->firstOrFail();
5155

5256
$this->project = $project;
57+
58+
// Load projects and environments for breadcrumb navigation (avoids inline queries in view)
59+
$this->allProjects = Project::ownedByCurrentTeamCached();
60+
$this->allEnvironments = $project->environments()
61+
->with([
62+
'applications.additional_servers',
63+
'applications.destination.server',
64+
'services',
65+
'services.destination.server',
66+
'postgresqls',
67+
'postgresqls.destination.server',
68+
'redis',
69+
'redis.destination.server',
70+
'mongodbs',
71+
'mongodbs.destination.server',
72+
'mysqls',
73+
'mysqls.destination.server',
74+
'mariadbs',
75+
'mariadbs.destination.server',
76+
'keydbs',
77+
'keydbs.destination.server',
78+
'dragonflies',
79+
'dragonflies.destination.server',
80+
'clickhouses',
81+
'clickhouses.destination.server',
82+
])->get();
83+
5384
$this->environment = $environment->loadCount([
5485
'applications',
5586
'redis',
@@ -71,11 +102,13 @@ public function mount()
71102
'destination.server.settings',
72103
'settings',
73104
])->get()->sortBy('name');
74-
$this->applications = $this->applications->map(function ($application) {
105+
$projectUuid = $this->project->uuid;
106+
$environmentUuid = $this->environment->uuid;
107+
$this->applications = $this->applications->map(function ($application) use ($projectUuid, $environmentUuid) {
75108
$application->hrefLink = route('project.application.configuration', [
76-
'project_uuid' => data_get($application, 'environment.project.uuid'),
77-
'environment_uuid' => data_get($application, 'environment.uuid'),
78-
'application_uuid' => data_get($application, 'uuid'),
109+
'project_uuid' => $projectUuid,
110+
'environment_uuid' => $environmentUuid,
111+
'application_uuid' => $application->uuid,
79112
]);
80113

81114
return $application;
@@ -98,11 +131,11 @@ public function mount()
98131
'tags',
99132
'destination.server.settings',
100133
])->get()->sortBy('name');
101-
$this->{$property} = $this->{$property}->map(function ($db) {
134+
$this->{$property} = $this->{$property}->map(function ($db) use ($projectUuid, $environmentUuid) {
102135
$db->hrefLink = route('project.database.configuration', [
103-
'project_uuid' => $this->project->uuid,
136+
'project_uuid' => $projectUuid,
104137
'database_uuid' => $db->uuid,
105-
'environment_uuid' => data_get($this->environment, 'uuid'),
138+
'environment_uuid' => $environmentUuid,
106139
]);
107140

108141
return $db;
@@ -114,11 +147,11 @@ public function mount()
114147
'tags',
115148
'destination.server.settings',
116149
])->get()->sortBy('name');
117-
$this->services = $this->services->map(function ($service) {
150+
$this->services = $this->services->map(function ($service) use ($projectUuid, $environmentUuid) {
118151
$service->hrefLink = route('project.service.configuration', [
119-
'project_uuid' => data_get($service, 'environment.project.uuid'),
120-
'environment_uuid' => data_get($service, 'environment.uuid'),
121-
'service_uuid' => data_get($service, 'uuid'),
152+
'project_uuid' => $projectUuid,
153+
'environment_uuid' => $environmentUuid,
154+
'service_uuid' => $service->uuid,
122155
]);
123156

124157
return $service;

app/Models/InstanceSettings.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Database\Eloquent\Casts\Attribute;
66
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Support\Once;
78
use Spatie\Url\Url;
89

910
class InstanceSettings extends Model
@@ -35,6 +36,9 @@ class InstanceSettings extends Model
3536
protected static function booted(): void
3637
{
3738
static::updated(function ($settings) {
39+
// Clear once() cache so subsequent calls get fresh data
40+
Once::flush();
41+
3842
// Clear trusted hosts cache when FQDN changes
3943
if ($settings->wasChanged('fqdn')) {
4044
\Cache::forget('instance_settings_fqdn_host');
@@ -82,7 +86,7 @@ public function autoUpdateFrequency(): Attribute
8286

8387
public static function get()
8488
{
85-
return InstanceSettings::findOrFail(0);
89+
return once(fn () => InstanceSettings::findOrFail(0));
8690
}
8791

8892
// public function getRecipients($notification)

app/Models/Server.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ class Server extends BaseModel
108108

109109
public static $batch_counter = 0;
110110

111+
/**
112+
* Identity map cache for request-scoped Server lookups.
113+
* Prevents N+1 queries when the same Server is accessed multiple times.
114+
*/
115+
private static ?array $identityMapCache = null;
116+
111117
protected $appends = ['is_coolify_host'];
112118

113119
protected static function booted()
@@ -186,6 +192,40 @@ protected static function booted()
186192
$server->settings()->delete();
187193
$server->sslCertificates()->delete();
188194
});
195+
196+
static::updated(function () {
197+
static::flushIdentityMap();
198+
});
199+
}
200+
201+
/**
202+
* Find a Server by ID using the identity map cache.
203+
* This prevents N+1 queries when the same Server is accessed multiple times.
204+
*/
205+
public static function findCached($id): ?static
206+
{
207+
if ($id === null) {
208+
return null;
209+
}
210+
211+
if (static::$identityMapCache === null) {
212+
static::$identityMapCache = [];
213+
}
214+
215+
if (! isset(static::$identityMapCache[$id])) {
216+
static::$identityMapCache[$id] = static::query()->find($id);
217+
}
218+
219+
return static::$identityMapCache[$id];
220+
}
221+
222+
/**
223+
* Flush the identity map cache.
224+
* Called automatically on update, and should be called in tests.
225+
*/
226+
public static function flushIdentityMap(): void
227+
{
228+
static::$identityMapCache = null;
189229
}
190230

191231
protected $casts = [

app/Models/StandaloneDocker.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ public function server()
7373
return $this->belongsTo(Server::class);
7474
}
7575

76+
/**
77+
* Get the server attribute using identity map caching.
78+
* This intercepts lazy-loading to use cached Server lookups.
79+
*/
80+
public function getServerAttribute(): ?Server
81+
{
82+
// Use eager loaded data if available
83+
if ($this->relationLoaded('server')) {
84+
return $this->getRelation('server');
85+
}
86+
87+
// Use identity map for lazy loading
88+
$server = Server::findCached($this->server_id);
89+
90+
// Cache in relation for future access on this instance
91+
if ($server) {
92+
$this->setRelation('server', $server);
93+
}
94+
95+
return $server;
96+
}
97+
7698
public function services()
7799
{
78100
return $this->morphMany(Service::class, 'destination');

app/Models/SwarmDocker.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,28 @@ public function server()
5656
return $this->belongsTo(Server::class);
5757
}
5858

59+
/**
60+
* Get the server attribute using identity map caching.
61+
* This intercepts lazy-loading to use cached Server lookups.
62+
*/
63+
public function getServerAttribute(): ?Server
64+
{
65+
// Use eager loaded data if available
66+
if ($this->relationLoaded('server')) {
67+
return $this->getRelation('server');
68+
}
69+
70+
// Use identity map for lazy loading
71+
$server = Server::findCached($this->server_id);
72+
73+
// Cache in relation for future access on this instance
74+
if ($server) {
75+
$this->setRelation('server', $server);
76+
}
77+
78+
return $server;
79+
}
80+
5981
public function services()
6082
{
6183
return $this->morphMany(Service::class, 'destination');

config/constants.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
return [
44
'coolify' => [
5-
'version' => '4.0.0-beta.461',
5+
'version' => '4.0.0-beta.462',
66
'helper_version' => '1.0.12',
77
'realtime_version' => '1.0.10',
88
'self_hosted' => env('SELF_HOSTED', true),

other/nightly/versions.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"coolify": {
33
"v4": {
4-
"version": "4.0.0-beta.461"
4+
"version": "4.0.0-beta.462"
55
},
66
"nightly": {
7-
"version": "4.0.0-beta.462"
7+
"version": "4.0.0-beta.463"
88
},
99
"helper": {
1010
"version": "1.0.12"

resources/views/components/resources/breadcrumbs.blade.php

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,28 @@
22
'lastDeploymentInfo' => null,
33
'lastDeploymentLink' => null,
44
'resource' => null,
5+
'projects' => null,
6+
'environments' => null,
57
])
68
@php
7-
$projects = auth()->user()->currentTeam()->projects()->get();
8-
$environments = $resource->environment->project
9+
use App\Models\Project;
10+
11+
// Use passed props if available, otherwise query (backwards compatible)
12+
$projects = $projects ?? Project::ownedByCurrentTeamCached();
13+
$environments = $environments ?? $resource->environment->project
914
->environments()
10-
->with(['applications', 'services'])
15+
->with([
16+
'applications',
17+
'services',
18+
'postgresqls',
19+
'redis',
20+
'mongodbs',
21+
'mysqls',
22+
'mariadbs',
23+
'keydbs',
24+
'dragonflies',
25+
'clickhouses',
26+
])
1127
->get();
1228
$currentProjectUuid = data_get($resource, 'environment.project.uuid');
1329
$currentEnvironmentUuid = data_get($resource, 'environment.uuid');
@@ -74,16 +90,24 @@ class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolg
7490
class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
7591
@foreach ($environments as $environment)
7692
@php
93+
// Use pre-loaded relations instead of databases() method to avoid N+1 queries
94+
$envDatabases = collect()
95+
->merge($environment->postgresqls ?? collect())
96+
->merge($environment->redis ?? collect())
97+
->merge($environment->mongodbs ?? collect())
98+
->merge($environment->mysqls ?? collect())
99+
->merge($environment->mariadbs ?? collect())
100+
->merge($environment->keydbs ?? collect())
101+
->merge($environment->dragonflies ?? collect())
102+
->merge($environment->clickhouses ?? collect());
77103
$envResources = collect()
78104
->merge(
79105
$environment->applications->map(
80106
fn($app) => ['type' => 'application', 'resource' => $app],
81107
),
82108
)
83109
->merge(
84-
$environment
85-
->databases()
86-
->map(fn($db) => ['type' => 'database', 'resource' => $db]),
110+
$envDatabases->map(fn($db) => ['type' => 'database', 'resource' => $db]),
87111
)
88112
->merge(
89113
$environment->services->map(
@@ -173,7 +197,9 @@ class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 bor
173197
]),
174198
};
175199
$isCurrentResource = $res->uuid === $currentResourceUuid;
176-
$resHasMultipleServers = $resType === 'application' && method_exists($res, 'additional_servers') && $res->additional_servers()->count() > 0;
200+
// Use loaded relation count if available, otherwise check additional_servers_count attribute
201+
$resHasMultipleServers = $resType === 'application' && method_exists($res, 'additional_servers') &&
202+
($res->relationLoaded('additional_servers') ? $res->additional_servers->count() > 0 : ($res->additional_servers_count ?? 0) > 0);
177203
$resServerName = $resHasMultipleServers ? null : data_get($res, 'destination.server.name');
178204
@endphp
179205
<div @mouseenter="openRes('{{ $environment->uuid }}-{{ $res->uuid }}'); resPositions['{{ $environment->uuid }}-{{ $res->uuid }}'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
@@ -405,7 +431,9 @@ class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolg
405431
$isApplication = $resourceType === 'App\Models\Application';
406432
$isService = $resourceType === 'App\Models\Service';
407433
$isDatabase = str_contains($resourceType, 'Database') || str_contains($resourceType, 'Standalone');
408-
$hasMultipleServers = $isApplication && method_exists($resource, 'additional_servers') && $resource->additional_servers()->count() > 0;
434+
// Use loaded relation count if available, otherwise check additional_servers_count attribute
435+
$hasMultipleServers = $isApplication && method_exists($resource, 'additional_servers') &&
436+
($resource->relationLoaded('additional_servers') ? $resource->additional_servers->count() > 0 : ($resource->additional_servers_count ?? 0) > 0);
409437
$serverName = $hasMultipleServers ? null : data_get($resource, 'destination.server.name');
410438
$routeParams = [
411439
'project_uuid' => $currentProjectUuid,

resources/views/livewire/project/index.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2 -mt-1">
1515
@foreach ($projects as $project)
1616
<div class="relative gap-2 cursor-pointer coolbox group">
17-
<a href="{{ $project->navigateTo() }}" class="absolute inset-0"></a>
17+
<a href="{{ $project->navigateTo() }}" {{ wireNavigate() }} class="absolute inset-0"></a>
1818
<div class="flex flex-1 mx-6">
1919
<div class="flex flex-col justify-center flex-1">
2020
<div class="box-title">{{ $project->name }}</div>

0 commit comments

Comments
 (0)