fix(services): link Website button to project page, not host root

Closes #11752
This commit is contained in:
Andras Bacsai
2026-10-16 21:35:02 +02:00
parent ed8271eb9f
commit b5e1995cac
3 changed files with 52 additions and 11 deletions
+24
View File
@@ -118,6 +118,7 @@ class Select extends Component
'name' => str($serviceKey)->headline(),
'docsSlug' => str($serviceKey)->lower()->value(),
'templateLastUpdated' => $templateLastUpdatedMap[$serviceKey] ?? null,
'website' => $this->serviceWebsiteUrl(data_get($service, 'documentation')),
] + service_logo_urls(data_get($service, 'logo')) + (array) $service;
})->all();
@@ -288,6 +289,29 @@ class Select extends Component
}
}
/**
* Derive a website link from the documentation URL. Shared hosts keep the
* path that identifies the project, so the link does not open the host root.
*/
private function serviceWebsiteUrl(?string $documentation): ?string
{
$url = parse_url((string) $documentation);
if (! in_array($url['scheme'] ?? null, ['http', 'https'], true) || blank($url['host'] ?? null)) {
return null;
}
$projectPathSegments = [
'github.com' => 2,
'gitlab.com' => 2,
'codeberg.org' => 2,
'hub.docker.com' => 3,
][str($url['host'])->lower()->chopStart('www.')->value()] ?? 0;
$projectPath = str($url['path'] ?? '')->explode('/')->filter()->take($projectPathSegments)->implode('/');
return rtrim("{$url['scheme']}://{$url['host']}/{$projectPath}", '/');
}
private function serviceTemplatesLastUpdated(): ?string
{
$fetchedAt = get_service_templates_fetched_at();
@@ -317,7 +317,7 @@
class="button" :class="{ 'opacity-60': docCheckInProgress[service.name] }">
Docs
</a>
<a x-show="serviceWebsiteUrl(service)" :href="serviceWebsiteUrl(service)"
<a x-show="service.website" :href="service.website"
target="_blank" rel="noopener noreferrer" class="button" @click.stop>
Website
</a>
@@ -471,16 +471,6 @@
databaseWebsiteUrl(database) {
return this.databaseLinks[database.id]?.website;
},
serviceWebsiteUrl(service) {
const source = service.website || service.homepage || service.documentation;
if (!source) return null;
try {
return new URL(source).origin;
} catch (error) {
return null;
}
},
async checkUrlExists(url) {
if (!url) return false;
try {
@@ -135,3 +135,30 @@ it('preserves one click service key casing when selecting a service template', f
expect($component->type)->toBe('one-click-service-denoKV');
});
it('links service websites to the project page instead of a shared host root', function () {
$path = base_path('templates/'.config('constants.services.file_name'));
$payload = json_encode([
'uptime-kuma' => ['documentation' => 'https://github.com/louislam/uptime-kuma?tab=readme-ov-file&utm_source=coolify.io'],
'alexandrie' => ['documentation' => 'https://www.github.com/Smaug6739/Alexandrie/tree/main/docs'],
'radarr' => ['documentation' => 'https://hub.docker.com/r/linuxserver/radarr?utm_source=coolify.io'],
'n8n' => ['documentation' => 'https://n8n.io/docs?utm_source=coolify.io'],
'broken' => ['documentation' => 'not a url'],
]);
File::partialMock()
->shouldReceive('exists')
->with($path)
->andReturn(true)
->shouldReceive('get')
->with($path)
->andReturn($payload);
$services = (new Select)->loadServices()['services'];
expect($services['uptime-kuma']['website'])->toBe('https://github.com/louislam/uptime-kuma')
->and($services['alexandrie']['website'])->toBe('https://www.github.com/Smaug6739/Alexandrie')
->and($services['radarr']['website'])->toBe('https://hub.docker.com/r/linuxserver/radarr')
->and($services['n8n']['website'])->toBe('https://n8n.io')
->and($services['broken']['website'])->toBeNull();
});