mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
feat(canvas): add interactive resource views and team avatars (#12160)
This commit is contained in:
@@ -26,15 +26,111 @@ function service_logo_urls(mixed $logo): array
|
||||
}
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\GitlabApp;
|
||||
use App\Models\LocalFileVolume;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Stringable;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* The URL of a logo in public/svgs/resources. The file's modification time is the version, so
|
||||
* browsers load a changed logo instead of their cached copy.
|
||||
*/
|
||||
function resource_logo_url(string $name): string
|
||||
{
|
||||
$path = "svgs/resources/{$name}.svg";
|
||||
$modifiedAt = is_file(public_path($path)) ? filemtime(public_path($path)) : false;
|
||||
|
||||
return asset($path).($modifiedAt ? "?v={$modifiedAt}" : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* The logo of a resource, the same one shown on the "New resource" page: the database engine,
|
||||
* the service template, or how the application is built. Null when there is no specific logo.
|
||||
*
|
||||
* Applications with a domain also get the favicon URL of their first domain. The browser loads it
|
||||
* and shows it in place of the logo when it loads. Coolify itself never requests it.
|
||||
*
|
||||
* @return array{src: string, dark: ?string, fallback: ?string, padded: bool, favicon: ?string}|null
|
||||
*/
|
||||
function resource_logo(Model $resource): ?array
|
||||
{
|
||||
$resourceLogo = function (string $name, bool $hasDarkVariant = false): array {
|
||||
return [
|
||||
'src' => resource_logo_url($name),
|
||||
'dark' => $hasDarkVariant ? resource_logo_url("{$name}-dark") : null,
|
||||
'fallback' => null,
|
||||
'padded' => false,
|
||||
'favicon' => null,
|
||||
];
|
||||
};
|
||||
|
||||
if ($resource instanceof Service) {
|
||||
$templates = once(fn () => get_service_templates());
|
||||
$type = filled($resource->service_type) ? $resource->service_type : str($resource->name)->beforeLast('-')->value();
|
||||
$logo = data_get($templates->get(resolve_service_template_key($type, $templates)), 'logo');
|
||||
if (! is_string($logo) || $logo === '' || basename($logo) === 'default.webp') {
|
||||
return null;
|
||||
}
|
||||
$urls = service_logo_urls($logo);
|
||||
|
||||
return ['src' => $urls['logo'], 'dark' => null, 'fallback' => $urls['logo_cdn_url'], 'padded' => true, 'favicon' => null];
|
||||
}
|
||||
|
||||
if ($resource instanceof Application) {
|
||||
$logo = match (true) {
|
||||
$resource->build_pack === 'dockerimage' => $resourceLogo('docker-image'),
|
||||
$resource->build_pack === 'dockerfile' => $resourceLogo('dockerfile'),
|
||||
$resource->build_pack === 'dockercompose' => $resourceLogo('docker-compose'),
|
||||
$resource->source_type === GithubApp::class => $resourceLogo('github-app', true),
|
||||
$resource->source_type === GitlabApp::class => $resourceLogo('gitlab-app'),
|
||||
default => $resourceLogo('public-repo'),
|
||||
};
|
||||
|
||||
return [...$logo, 'favicon' => resource_favicon_url($resource->fqdn)];
|
||||
}
|
||||
|
||||
if (! method_exists($resource, 'type')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match (str($resource->type())->after('standalone-')->value()) {
|
||||
'postgresql' => $resourceLogo('postgres'),
|
||||
'mysql' => $resourceLogo('mysql'),
|
||||
'mariadb' => $resourceLogo('mariadb'),
|
||||
'mongodb' => $resourceLogo('mongodb'),
|
||||
'redis' => $resourceLogo('redis'),
|
||||
'keydb' => $resourceLogo('keydb', true),
|
||||
'dragonfly' => $resourceLogo('dragonfly', true),
|
||||
'clickhouse' => $resourceLogo('clickhouse'),
|
||||
'sqlite' => $resourceLogo('sqlite'),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The favicon URL of the first domain in a comma-separated domain list, or null when there is no usable domain.
|
||||
*/
|
||||
function resource_favicon_url(?string $fqdn): ?string
|
||||
{
|
||||
$domain = str((string) $fqdn)->explode(',')->map(fn (string $url) => trim($url))->first(fn (string $url) => $url !== '');
|
||||
$parts = parse_url((string) $domain);
|
||||
$scheme = strtolower((string) ($parts['scheme'] ?? ''));
|
||||
$host = (string) ($parts['host'] ?? '');
|
||||
if (! in_array($scheme, ['http', 'https'], true) || $host === '' || str_contains($host, '*')) {
|
||||
return null;
|
||||
}
|
||||
$port = isset($parts['port']) ? ':'.$parts['port'] : '';
|
||||
|
||||
return "{$scheme}://{$host}{$port}/favicon.ico";
|
||||
}
|
||||
|
||||
function replaceRegex(?string $name = null)
|
||||
{
|
||||
return "/\\\${?{$name}[^}]*}?|\\\${$name}\w+/";
|
||||
|
||||
@@ -892,6 +892,10 @@ function profile_avatar_url(User $user): string
|
||||
}
|
||||
}
|
||||
|
||||
if (auth()->check() && $user->id !== auth()->id()) {
|
||||
return route('team.member.avatar', ['user_id' => $user->id, 'v' => $user->updated_at->timestamp]);
|
||||
}
|
||||
|
||||
return route('profile.avatar', ['v' => $user->updated_at->timestamp]);
|
||||
}
|
||||
|
||||
@@ -910,6 +914,21 @@ function project_icon_url(Project $project): string
|
||||
]);
|
||||
}
|
||||
|
||||
function team_avatar_url(Team $team): string
|
||||
{
|
||||
if ($team->avatar_storage_type === 's3') {
|
||||
$url = s3_image_url($team->avatar_s3_storage_id, $team->avatar_path, $team->updated_at->timestamp);
|
||||
if ($url) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
return route('team.avatar', [
|
||||
'team_id' => $team->id,
|
||||
'v' => $team->updated_at->timestamp,
|
||||
]);
|
||||
}
|
||||
|
||||
function s3_image_url(?int $storageId, ?string $path, int $version): ?string
|
||||
{
|
||||
if (! $storageId || blank($path)) {
|
||||
@@ -4601,6 +4620,10 @@ function settingsSearchItems(iterable $groupedItems, array $routeParameters, arr
|
||||
|
||||
foreach ($groupedItems as $groupLabel => $groupItems) {
|
||||
foreach ($groupItems as $item) {
|
||||
if ($item['disabled'] ?? false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$href = route($item['route'], $routeParameters);
|
||||
$add($item['label'], $groupLabel, $href, $item['navigate'] ?? true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user