mirror of
https://github.com/coollabsio/coolify.git
synced 2026-06-13 19:09:50 +00:00
feat(services): show template update timestamps
This commit is contained in:
@@ -4,7 +4,9 @@ namespace App\Livewire\Project\New;
|
|||||||
|
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Carbon\CarbonImmutable;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Select extends Component
|
class Select extends Component
|
||||||
@@ -105,7 +107,9 @@ class Select extends Component
|
|||||||
public function loadServices()
|
public function loadServices()
|
||||||
{
|
{
|
||||||
$services = get_service_templates();
|
$services = get_service_templates();
|
||||||
$services = collect($services)->map(function ($service, $key) {
|
$templateLastUpdatedMap = $this->serviceTemplateLastUpdatedMap($services->keys());
|
||||||
|
|
||||||
|
$services = collect($services)->map(function ($service, $key) use ($templateLastUpdatedMap) {
|
||||||
$default_logo = 'images/default.webp';
|
$default_logo = 'images/default.webp';
|
||||||
$logo = data_get($service, 'logo', $default_logo);
|
$logo = data_get($service, 'logo', $default_logo);
|
||||||
$local_logo_path = public_path($logo);
|
$local_logo_path = public_path($logo);
|
||||||
@@ -116,6 +120,7 @@ class Select extends Component
|
|||||||
'logo_github_url' => file_exists($local_logo_path)
|
'logo_github_url' => file_exists($local_logo_path)
|
||||||
? 'https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/main/public/'.$logo
|
? 'https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/main/public/'.$logo
|
||||||
: asset($default_logo),
|
: asset($default_logo),
|
||||||
|
'templateLastUpdated' => $templateLastUpdatedMap[(string) $key] ?? null,
|
||||||
] + (array) $service;
|
] + (array) $service;
|
||||||
})->all();
|
})->all();
|
||||||
|
|
||||||
@@ -247,6 +252,7 @@ class Select extends Component
|
|||||||
];
|
];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
'serviceTemplatesLastUpdated' => $this->serviceTemplatesLastUpdated(),
|
||||||
'services' => $services,
|
'services' => $services,
|
||||||
'categories' => $categories,
|
'categories' => $categories,
|
||||||
'gitBasedApplications' => $gitBasedApplications,
|
'gitBasedApplications' => $gitBasedApplications,
|
||||||
@@ -268,6 +274,55 @@ class Select extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function serviceTemplatesLastUpdated(): ?string
|
||||||
|
{
|
||||||
|
return $this->formatLastModified($this->serviceTemplatesPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function serviceTemplateLastUpdatedMap(Collection $serviceNames): array
|
||||||
|
{
|
||||||
|
$bundleMtime = file_exists($this->serviceTemplatesPath()) ? filemtime($this->serviceTemplatesPath()) : 0;
|
||||||
|
|
||||||
|
return Cache::remember(
|
||||||
|
"service-template-last-updated-map:{$bundleMtime}",
|
||||||
|
now()->addDay(),
|
||||||
|
fn () => $serviceNames
|
||||||
|
->mapWithKeys(fn ($serviceName) => [
|
||||||
|
(string) $serviceName => $this->serviceTemplateLastUpdated((string) $serviceName),
|
||||||
|
])
|
||||||
|
->all()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function serviceTemplateLastUpdated(string $serviceName): ?string
|
||||||
|
{
|
||||||
|
foreach (['yaml', 'yml'] as $extension) {
|
||||||
|
$templatePath = base_path("templates/compose/{$serviceName}.{$extension}");
|
||||||
|
|
||||||
|
if (file_exists($templatePath)) {
|
||||||
|
return $this->formatLastModified($templatePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function serviceTemplatesPath(): string
|
||||||
|
{
|
||||||
|
return base_path('templates/'.config('constants.services.file_name'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatLastModified(string $path): ?string
|
||||||
|
{
|
||||||
|
if (! file_exists($path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CarbonImmutable::createFromTimestamp(filemtime($path))
|
||||||
|
->timezone(config('app.timezone'))
|
||||||
|
->format('M j, Y H:i');
|
||||||
|
}
|
||||||
|
|
||||||
public function setType(string $type)
|
public function setType(string $type)
|
||||||
{
|
{
|
||||||
$type = str($type)->lower()->slug()->value();
|
$type = str($type)->lower()->slug()->value();
|
||||||
|
|||||||
@@ -138,9 +138,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div x-show="filteredServices.length > 0" class="mt-8">
|
<div x-show="filteredServices.length > 0" class="mt-8">
|
||||||
<div class="flex items-center gap-4" x-init="loadResources">
|
<div class="flex flex-wrap items-center gap-4" x-init="loadResources">
|
||||||
<h2>Services</h2>
|
<h2>Services</h2>
|
||||||
<x-forms.button x-on:click="loadResources">Reload List</x-forms.button>
|
<x-forms.button x-on:click="loadResources">Reload List</x-forms.button>
|
||||||
|
<div x-show="serviceTemplatesLastUpdated"
|
||||||
|
class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||||
|
Last Updated on Service Templates:
|
||||||
|
<span x-text="serviceTemplatesLastUpdated"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<x-callout type="info" title="Trademarks Policy" class="mt-4 mb-6">
|
<x-callout type="info" title="Trademarks Policy" class="mt-4 mb-6">
|
||||||
The respective trademarks mentioned here are owned by the respective companies, and use of them
|
The respective trademarks mentioned here are owned by the respective companies, and use of them
|
||||||
@@ -154,7 +159,14 @@
|
|||||||
<x-resource-view>
|
<x-resource-view>
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
<template x-if="service.name">
|
<template x-if="service.name">
|
||||||
<span x-text="service.name"></span>
|
<div>
|
||||||
|
<span x-text="service.name"></span>
|
||||||
|
<template x-if="service.templateLastUpdated">
|
||||||
|
<div class="mt-1 text-[0.7rem] font-normal text-neutral-500 dark:text-neutral-500">
|
||||||
|
Updated: <span x-text="service.templateLastUpdated"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-slot:description>
|
<x-slot:description>
|
||||||
@@ -237,6 +249,7 @@
|
|||||||
isSticky: false,
|
isSticky: false,
|
||||||
selecting: false,
|
selecting: false,
|
||||||
services: [],
|
services: [],
|
||||||
|
serviceTemplatesLastUpdated: null,
|
||||||
gitBasedApplications: [],
|
gitBasedApplications: [],
|
||||||
dockerBasedApplications: [],
|
dockerBasedApplications: [],
|
||||||
databases: [],
|
databases: [],
|
||||||
@@ -251,12 +264,14 @@
|
|||||||
this.loading = true;
|
this.loading = true;
|
||||||
const {
|
const {
|
||||||
services,
|
services,
|
||||||
|
serviceTemplatesLastUpdated,
|
||||||
categories,
|
categories,
|
||||||
gitBasedApplications,
|
gitBasedApplications,
|
||||||
dockerBasedApplications,
|
dockerBasedApplications,
|
||||||
databases
|
databases
|
||||||
} = await this.$wire.loadServices();
|
} = await this.$wire.loadServices();
|
||||||
this.services = services;
|
this.services = services;
|
||||||
|
this.serviceTemplatesLastUpdated = serviceTemplatesLastUpdated;
|
||||||
this.categories = categories || [];
|
this.categories = categories || [];
|
||||||
this.gitBasedApplications = gitBasedApplications;
|
this.gitBasedApplications = gitBasedApplications;
|
||||||
this.dockerBasedApplications = dockerBasedApplications;
|
this.dockerBasedApplications = dockerBasedApplications;
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Livewire\Project\New\Select;
|
||||||
|
use Carbon\CarbonImmutable;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\View;
|
||||||
|
use Illuminate\Support\ViewErrorBag;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Cache::flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the service templates bundle last updated timestamp', function () {
|
||||||
|
$component = new Select;
|
||||||
|
$templatePath = base_path('templates/'.config('constants.services.file_name'));
|
||||||
|
|
||||||
|
$resources = $component->loadServices();
|
||||||
|
|
||||||
|
expect($resources)
|
||||||
|
->toHaveKey('serviceTemplatesLastUpdated')
|
||||||
|
->and($resources['serviceTemplatesLastUpdated'])
|
||||||
|
->toBe(CarbonImmutable::createFromTimestamp(filemtime($templatePath))->timezone(config('app.timezone'))->format('M j, Y H:i'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns each service template last updated timestamp', function () {
|
||||||
|
$component = new Select;
|
||||||
|
$templatePath = base_path('templates/compose/activepieces.yaml');
|
||||||
|
|
||||||
|
$resources = $component->loadServices();
|
||||||
|
|
||||||
|
expect($resources['services']['activepieces'])
|
||||||
|
->toHaveKey('templateLastUpdated')
|
||||||
|
->and($resources['services']['activepieces']['templateLastUpdated'])
|
||||||
|
->toBe(CarbonImmutable::createFromTimestamp(filemtime($templatePath))->timezone(config('app.timezone'))->format('M j, Y H:i'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses a service template timestamp cache keyed by bundle mtime', function () {
|
||||||
|
$bundleMtime = filemtime(base_path('templates/'.config('constants.services.file_name')));
|
||||||
|
Cache::put("service-template-last-updated-map:{$bundleMtime}", [
|
||||||
|
'activepieces' => 'Cached timestamp',
|
||||||
|
], now()->addDay());
|
||||||
|
|
||||||
|
$resources = (new Select)->loadServices();
|
||||||
|
|
||||||
|
expect($resources['services']['activepieces']['templateLastUpdated'])->toBe('Cached timestamp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not use stale service template timestamp cache entries from another bundle mtime', function () {
|
||||||
|
$bundleMtime = filemtime(base_path('templates/'.config('constants.services.file_name')));
|
||||||
|
Cache::put('service-template-last-updated-map:'.($bundleMtime - 1), [
|
||||||
|
'activepieces' => 'Stale cached timestamp',
|
||||||
|
], now()->addDay());
|
||||||
|
|
||||||
|
$resources = (new Select)->loadServices();
|
||||||
|
|
||||||
|
expect($resources['services']['activepieces']['templateLastUpdated'])->not->toBe('Stale cached timestamp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the service templates last updated hint placeholder', function () {
|
||||||
|
View::share('errors', new ViewErrorBag);
|
||||||
|
|
||||||
|
$view = $this->view('livewire.project.new.select', [
|
||||||
|
'current_step' => 'type',
|
||||||
|
'environments' => collect(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$view->assertSee('Last Updated on Service Templates:');
|
||||||
|
$view->assertSee('serviceTemplatesLastUpdated');
|
||||||
|
$view->assertSee('service.templateLastUpdated');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user