Merge branch 'next' into feature/s3-path-prefix

This commit is contained in:
Karl Bauer 2026-01-17 17:51:39 +01:00 committed by GitHub
commit ffb1371094
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 7821 additions and 8689 deletions

12823
CHANGELOG.md

File diff suppressed because it is too large Load diff

View file

@ -141,6 +141,7 @@ Thank you so much!
<a href="https://github.com/Niki2k1"><img width="60px" alt="Niklas Lausch" src="https://github.com/Niki2k1.png"/></a>
<a href="https://capgo.app/?utm_source=coolify.io"><img width="60px" alt="Cap-go" src="https://github.com/cap-go.png"/></a>
<a href="https://interviewpal.com/?utm_source=coolify.io"><img width="60px" alt="InterviewPal" src="/public/svgs/interviewpal.svg"/></a>
<a href="https://transcript.lol/?utm_source=coolify.io"><img width="60px" alt="Transcript LOL" src="https://transcript.lol/logo.png"/></a>
...and many more at [GitHub Sponsors](https://github.com/sponsors/coollabsio)

File diff suppressed because it is too large Load diff

View file

@ -389,7 +389,7 @@ class DatabasesController extends Controller
], 422);
}
$postgresConf = base64_decode($request->postgres_conf);
if (mb_detect_encoding($postgresConf, 'ASCII', true) === false) {
if (mb_detect_encoding($postgresConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -429,7 +429,7 @@ class DatabasesController extends Controller
], 422);
}
$redisConf = base64_decode($request->redis_conf);
if (mb_detect_encoding($redisConf, 'ASCII', true) === false) {
if (mb_detect_encoding($redisConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -456,7 +456,7 @@ class DatabasesController extends Controller
], 422);
}
$keydbConf = base64_decode($request->keydb_conf);
if (mb_detect_encoding($keydbConf, 'ASCII', true) === false) {
if (mb_detect_encoding($keydbConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -486,7 +486,7 @@ class DatabasesController extends Controller
], 422);
}
$mariadbConf = base64_decode($request->mariadb_conf);
if (mb_detect_encoding($mariadbConf, 'ASCII', true) === false) {
if (mb_detect_encoding($mariadbConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -515,7 +515,7 @@ class DatabasesController extends Controller
], 422);
}
$mongoConf = base64_decode($request->mongo_conf);
if (mb_detect_encoding($mongoConf, 'ASCII', true) === false) {
if (mb_detect_encoding($mongoConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -546,7 +546,7 @@ class DatabasesController extends Controller
], 422);
}
$mysqlConf = base64_decode($request->mysql_conf);
if (mb_detect_encoding($mysqlConf, 'ASCII', true) === false) {
if (mb_detect_encoding($mysqlConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -1701,7 +1701,7 @@ class DatabasesController extends Controller
], 422);
}
$postgresConf = base64_decode($request->postgres_conf);
if (mb_detect_encoding($postgresConf, 'ASCII', true) === false) {
if (mb_detect_encoding($postgresConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -1756,7 +1756,7 @@ class DatabasesController extends Controller
], 422);
}
$mariadbConf = base64_decode($request->mariadb_conf);
if (mb_detect_encoding($mariadbConf, 'ASCII', true) === false) {
if (mb_detect_encoding($mariadbConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -1815,7 +1815,7 @@ class DatabasesController extends Controller
], 422);
}
$mysqlConf = base64_decode($request->mysql_conf);
if (mb_detect_encoding($mysqlConf, 'ASCII', true) === false) {
if (mb_detect_encoding($mysqlConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -1871,7 +1871,7 @@ class DatabasesController extends Controller
], 422);
}
$redisConf = base64_decode($request->redis_conf);
if (mb_detect_encoding($redisConf, 'ASCII', true) === false) {
if (mb_detect_encoding($redisConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -1957,7 +1957,7 @@ class DatabasesController extends Controller
], 422);
}
$keydbConf = base64_decode($request->keydb_conf);
if (mb_detect_encoding($keydbConf, 'ASCII', true) === false) {
if (mb_detect_encoding($keydbConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -2051,7 +2051,7 @@ class DatabasesController extends Controller
], 422);
}
$mongoConf = base64_decode($request->mongo_conf);
if (mb_detect_encoding($mongoConf, 'ASCII', true) === false) {
if (mb_detect_encoding($mongoConf, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [

View file

@ -12,6 +12,7 @@ use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use OpenApi\Attributes as OA;
use Symfony\Component\Yaml\Yaml;
@ -37,6 +38,100 @@ class ServicesController extends Controller
return serializeApiResponse($service);
}
private function applyServiceUrls(Service $service, array $urlsArray, string $teamId, bool $forceDomainOverride = false): ?array
{
$errors = [];
$conflicts = [];
$urls = collect($urlsArray)->flatMap(function ($item) {
$urlValue = data_get($item, 'url');
if (blank($urlValue)) {
return [];
}
return str($urlValue)->replaceStart(',', '')->replaceEnd(',', '')->trim()->explode(',')->map(fn ($url) => trim($url))->filter();
});
$urls = $urls->map(function ($url) use (&$errors) {
if (! filter_var($url, FILTER_VALIDATE_URL)) {
$errors[] = "Invalid URL: {$url}";
return $url;
}
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
if (! in_array(strtolower($scheme), ['http', 'https'])) {
$errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
}
return $url;
});
$duplicates = $urls->duplicates()->unique()->values();
if ($duplicates->isNotEmpty() && ! $forceDomainOverride) {
$errors[] = 'The current request contains conflicting URLs across containers: '.implode(', ', $duplicates->toArray()).'. Use force_domain_override=true to proceed.';
}
if (count($errors) > 0) {
return ['errors' => $errors];
}
collect($urlsArray)->each(function ($item) use ($service, $teamId, $forceDomainOverride, &$errors, &$conflicts) {
$name = data_get($item, 'name');
$containerUrls = data_get($item, 'url');
if (blank($name)) {
$errors[] = 'Service container name is required to apply URLs.';
return;
}
$application = $service->applications()->where('name', $name)->first();
if (! $application) {
$errors[] = "Service container with '{$name}' not found.";
return;
}
if (filled($containerUrls)) {
$containerUrls = str($containerUrls)->replaceStart(',', '')->replaceEnd(',', '')->trim();
$containerUrls = str($containerUrls)->explode(',')->map(fn ($url) => str(trim($url))->lower());
$result = checkIfDomainIsAlreadyUsedViaAPI($containerUrls, $teamId, $application->uuid);
if (isset($result['error'])) {
$errors[] = $result['error'];
return;
}
if ($result['hasConflicts'] && ! $forceDomainOverride) {
$conflicts = array_merge($conflicts, $result['conflicts']);
return;
}
$containerUrls = $containerUrls->filter(fn ($u) => filled($u))->unique()->implode(',');
} else {
$containerUrls = null;
}
$application->fqdn = $containerUrls;
$application->save();
});
if (! empty($errors)) {
return ['errors' => $errors];
}
if (! empty($conflicts)) {
return [
'conflicts' => $conflicts,
'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.',
];
}
return null;
}
#[OA\Get(
summary: 'List',
description: 'List all services.',
@ -105,98 +200,7 @@ class ServicesController extends Controller
type: 'object',
required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'],
properties: [
'type' => [
'description' => 'The one-click service type',
'type' => 'string',
'enum' => [
'activepieces',
'appsmith',
'appwrite',
'authentik',
'babybuddy',
'budge',
'changedetection',
'chatwoot',
'classicpress-with-mariadb',
'classicpress-with-mysql',
'classicpress-without-database',
'cloudflared',
'code-server',
'dashboard',
'directus',
'directus-with-postgresql',
'docker-registry',
'docuseal',
'docuseal-with-postgres',
'dokuwiki',
'duplicati',
'emby',
'embystat',
'fider',
'filebrowser',
'firefly',
'formbricks',
'ghost',
'gitea',
'gitea-with-mariadb',
'gitea-with-mysql',
'gitea-with-postgresql',
'glance',
'glances',
'glitchtip',
'grafana',
'grafana-with-postgresql',
'grocy',
'heimdall',
'homepage',
'jellyfin',
'kuzzle',
'listmonk',
'logto',
'mediawiki',
'meilisearch',
'metabase',
'metube',
'minio',
'moodle',
'n8n',
'n8n-with-postgresql',
'next-image-transformation',
'nextcloud',
'nocodb',
'odoo',
'openblocks',
'pairdrop',
'penpot',
'phpmyadmin',
'pocketbase',
'posthog',
'reactive-resume',
'rocketchat',
'shlink',
'slash',
'snapdrop',
'statusnook',
'stirling-pdf',
'supabase',
'syncthing',
'tolgee',
'trigger',
'trigger-with-external-database',
'twenty',
'umami',
'unleash-with-postgresql',
'unleash-without-database',
'uptime-kuma',
'vaultwarden',
'vikunja',
'weblate',
'whoogle',
'wordpress-with-mariadb',
'wordpress-with-mysql',
'wordpress-without-database',
],
],
'type' => ['description' => 'The one-click service type (e.g. "actualbudget", "calibre-web", "gitea-with-mysql" ...)', 'type' => 'string'],
'name' => ['type' => 'string', 'maxLength' => 255, 'description' => 'Name of the service.'],
'description' => ['type' => 'string', 'nullable' => true, 'description' => 'Description of the service.'],
'project_uuid' => ['type' => 'string', 'description' => 'Project UUID.'],
@ -205,7 +209,19 @@ class ServicesController extends Controller
'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'],
'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'],
'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'],
'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'],
'docker_compose_raw' => ['type' => 'string', 'description' => 'The base64 encoded Docker Compose content.'],
'urls' => [
'type' => 'array',
'description' => 'Array of URLs to be applied to containers of a service.',
'items' => new OA\Schema(
type: 'object',
properties: [
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
'url' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").'],
],
),
],
'force_domain_override' => ['type' => 'boolean', 'default' => false, 'description' => 'Force domain override even if conflicts are detected.'],
],
),
),
@ -235,6 +251,35 @@ class ServicesController extends Controller
response: 400,
ref: '#/components/responses/400',
),
new OA\Response(
response: 409,
description: 'Domain conflicts detected.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.'],
'warning' => ['type' => 'string', 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.'],
'conflicts' => [
'type' => 'array',
'items' => new OA\Schema(
type: 'object',
properties: [
'domain' => ['type' => 'string', 'example' => 'example.com'],
'resource_name' => ['type' => 'string', 'example' => 'My Application'],
'resource_uuid' => ['type' => 'string', 'nullable' => true, 'example' => 'abc123-def456'],
'resource_type' => ['type' => 'string', 'enum' => ['application', 'service', 'instance'], 'example' => 'application'],
'message' => ['type' => 'string', 'example' => 'Domain example.com is already in use by application \'My Application\''],
]
),
],
]
)
),
]
),
new OA\Response(
response: 422,
ref: '#/components/responses/422',
@ -243,7 +288,7 @@ class ServicesController extends Controller
)]
public function create_service(Request $request)
{
$allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw'];
$allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'urls', 'force_domain_override'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
@ -256,7 +301,7 @@ class ServicesController extends Controller
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
$validationRules = [
'type' => 'string|required_without:docker_compose_raw',
'docker_compose_raw' => 'string|required_without:type',
'project_uuid' => 'string|required',
@ -267,7 +312,16 @@ class ServicesController extends Controller
'name' => 'string|max:255',
'description' => 'string|nullable',
'instant_deploy' => 'boolean',
]);
'urls' => 'array|nullable',
'urls.*' => 'array:name,url',
'urls.*.name' => 'string|required',
'urls.*.url' => 'string|nullable',
'force_domain_override' => 'boolean',
];
$validationMessages = [
'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.',
];
$validator = Validator::make($request->all(), $validationRules, $validationMessages);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
@ -283,6 +337,13 @@ class ServicesController extends Controller
'errors' => $errors,
], 422);
}
if (filled($request->type) && filled($request->docker_compose_raw)) {
return response()->json([
'message' => 'You cannot provide both service type and docker_compose_raw. Use one or the other.',
], 422);
}
$environmentUuid = $request->environment_uuid;
$environmentName = $request->environment_name;
if (blank($environmentUuid) && blank($environmentName)) {
@ -355,7 +416,8 @@ class ServicesController extends Controller
data_set($servicePayload, 'connect_to_docker_network', true);
}
$service = Service::create($servicePayload);
$service->name = "$oneClickServiceName-".$service->uuid;
$service->name = $request->name ?? "$oneClickServiceName-".$service->uuid;
$service->description = $request->description;
$service->save();
if ($oneClickDotEnvs?->count() > 0) {
$oneClickDotEnvs->each(function ($value) use ($service) {
@ -380,29 +442,41 @@ class ServicesController extends Controller
// Apply service-specific application prerequisites
applyServiceApplicationPrerequisites($service);
if ($request->has('urls') && is_array($request->urls)) {
$urlResult = $this->applyServiceUrls($service, $request->urls, $teamId, $request->boolean('force_domain_override'));
if ($urlResult !== null) {
$service->delete();
if (isset($urlResult['errors'])) {
return response()->json([
'message' => 'Validation failed.',
'errors' => $urlResult['errors'],
], 422);
}
if (isset($urlResult['conflicts'])) {
return response()->json([
'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.',
'conflicts' => $urlResult['conflicts'],
'warning' => $urlResult['warning'],
], 409);
}
}
}
if ($instantDeploy) {
StartService::dispatch($service);
}
$domains = $service->applications()->get()->pluck('fqdn')->sort();
$domains = $domains->map(function ($domain) {
if (count(explode(':', $domain)) > 2) {
return str($domain)->beforeLast(':')->value();
}
return $domain;
});
return response()->json([
'uuid' => $service->uuid,
'domains' => $domains,
]);
'domains' => $service->applications()->pluck('fqdn')->filter()->sort()->values(),
])->setStatusCode(201);
}
return response()->json(['message' => 'Service not found.', 'valid_service_types' => $serviceKeys], 404);
} elseif (filled($request->docker_compose_raw)) {
$allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network'];
$allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls', 'force_domain_override'];
$validator = customApiValidator($request->all(), [
$validationRules = [
'project_uuid' => 'string|required',
'environment_name' => 'string|nullable',
'environment_uuid' => 'string|nullable',
@ -413,7 +487,16 @@ class ServicesController extends Controller
'instant_deploy' => 'boolean',
'connect_to_docker_network' => 'boolean',
'docker_compose_raw' => 'string|required',
]);
'urls' => 'array|nullable',
'urls.*' => 'array:name,url',
'urls.*.name' => 'string|required',
'urls.*.url' => 'string|nullable',
'force_domain_override' => 'boolean',
];
$validationMessages = [
'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.',
];
$validator = Validator::make($request->all(), $validationRules, $validationMessages);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
@ -469,7 +552,7 @@ class ServicesController extends Controller
], 422);
}
$dockerComposeRaw = base64_decode($request->docker_compose_raw);
if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) {
if (mb_detect_encoding($dockerComposeRaw, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -507,25 +590,40 @@ class ServicesController extends Controller
$service->save();
$service->parse(isNew: true);
if ($request->has('urls') && is_array($request->urls)) {
$urlResult = $this->applyServiceUrls($service, $request->urls, $teamId, $request->boolean('force_domain_override'));
if ($urlResult !== null) {
$service->delete();
if (isset($urlResult['errors'])) {
return response()->json([
'message' => 'Validation failed.',
'errors' => $urlResult['errors'],
], 422);
}
if (isset($urlResult['conflicts'])) {
return response()->json([
'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.',
'conflicts' => $urlResult['conflicts'],
'warning' => $urlResult['warning'],
], 409);
}
}
}
if ($instantDeploy) {
StartService::dispatch($service);
}
$domains = $service->applications()->get()->pluck('fqdn')->sort();
$domains = $domains->map(function ($domain) {
if (count(explode(':', $domain)) > 2) {
return str($domain)->beforeLast(':')->value();
}
return $domain;
})->values();
return response()->json([
'uuid' => $service->uuid,
'domains' => $domains,
'domains' => $service->applications()->pluck('fqdn')->filter()->sort()->values(),
])->setStatusCode(201);
} else {
return response()->json(['message' => 'No service type or docker_compose_raw provided.'], 400);
} elseif (filled($request->type)) {
return response()->json([
'message' => 'Invalid service type.',
'valid_service_types' => $serviceKeys,
], 404);
}
}
@ -702,7 +800,19 @@ class ServicesController extends Controller
'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the service should be deployed instantly.'],
'connect_to_docker_network' => ['type' => 'boolean', 'default' => false, 'description' => 'Connect the service to the predefined docker network.'],
'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'],
'docker_compose_raw' => ['type' => 'string', 'description' => 'The base64 encoded Docker Compose content.'],
'urls' => [
'type' => 'array',
'description' => 'Array of URLs to be applied to containers of a service.',
'items' => new OA\Schema(
type: 'object',
properties: [
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
'url' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").'],
],
),
],
'force_domain_override' => ['type' => 'boolean', 'default' => false, 'description' => 'Force domain override even if conflicts are detected.'],
],
)
),
@ -737,6 +847,35 @@ class ServicesController extends Controller
response: 404,
ref: '#/components/responses/404',
),
new OA\Response(
response: 409,
description: 'Domain conflicts detected.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.'],
'warning' => ['type' => 'string', 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.'],
'conflicts' => [
'type' => 'array',
'items' => new OA\Schema(
type: 'object',
properties: [
'domain' => ['type' => 'string', 'example' => 'example.com'],
'resource_name' => ['type' => 'string', 'example' => 'My Application'],
'resource_uuid' => ['type' => 'string', 'nullable' => true, 'example' => 'abc123-def456'],
'resource_type' => ['type' => 'string', 'enum' => ['application', 'service', 'instance'], 'example' => 'application'],
'message' => ['type' => 'string', 'example' => 'Domain example.com is already in use by application \'My Application\''],
]
),
],
]
)
),
]
),
new OA\Response(
response: 422,
ref: '#/components/responses/422',
@ -762,15 +901,24 @@ class ServicesController extends Controller
$this->authorize('update', $service);
$allowedFields = ['name', 'description', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network'];
$allowedFields = ['name', 'description', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network', 'urls', 'force_domain_override'];
$validator = customApiValidator($request->all(), [
$validationRules = [
'name' => 'string|max:255',
'description' => 'string|nullable',
'instant_deploy' => 'boolean',
'connect_to_docker_network' => 'boolean',
'docker_compose_raw' => 'string|nullable',
]);
'urls' => 'array|nullable',
'urls.*' => 'array:name,url',
'urls.*.name' => 'string|required',
'urls.*.url' => 'string|nullable',
'force_domain_override' => 'boolean',
];
$validationMessages = [
'urls.*.array' => 'An item in the urls array has invalid fields. Only name and url fields are supported.',
];
$validator = Validator::make($request->all(), $validationRules, $validationMessages);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
@ -796,7 +944,7 @@ class ServicesController extends Controller
], 422);
}
$dockerComposeRaw = base64_decode($request->docker_compose_raw);
if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) {
if (mb_detect_encoding($dockerComposeRaw, 'UTF-8', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
@ -834,22 +982,33 @@ class ServicesController extends Controller
$service->save();
$service->parse();
if ($request->has('urls') && is_array($request->urls)) {
$urlResult = $this->applyServiceUrls($service, $request->urls, $teamId, $request->boolean('force_domain_override'));
if ($urlResult !== null) {
if (isset($urlResult['errors'])) {
return response()->json([
'message' => 'Validation failed.',
'errors' => $urlResult['errors'],
], 422);
}
if (isset($urlResult['conflicts'])) {
return response()->json([
'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.',
'conflicts' => $urlResult['conflicts'],
'warning' => $urlResult['warning'],
], 409);
}
}
}
if ($request->instant_deploy) {
StartService::dispatch($service);
}
$domains = $service->applications()->get()->pluck('fqdn')->sort();
$domains = $domains->map(function ($domain) {
if (count(explode(':', $domain)) > 2) {
return str($domain)->beforeLast(':')->value();
}
return $domain;
})->values();
return response()->json([
'uuid' => $service->uuid,
'domains' => $domains,
'domains' => $service->applications()->pluck('fqdn')->filter()->sort()->values(),
])->setStatusCode(200);
}

View file

@ -1390,14 +1390,18 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/.env > /dev/null"),
],
[
executeInDocker($this->deployment_uuid, "cat $this->workdir/.env"),
'hidden' => true,
]
);
if (isDev()) {
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "cat $this->workdir/.env"),
'hidden' => true,
]
);
}
// Write .env file to configuration directory
if ($this->use_build_server) {
$this->server = $this->mainServer;
@ -1649,12 +1653,17 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee ".self::BUILD_TIME_ENV_PATH.' > /dev/null'),
],
[
executeInDocker($this->deployment_uuid, 'cat '.self::BUILD_TIME_ENV_PATH),
'hidden' => true,
],
]
);
if (isDev()) {
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, 'cat '.self::BUILD_TIME_ENV_PATH),
'hidden' => true,
]
);
}
} elseif ($this->build_pack === 'dockercompose' || $this->build_pack === 'dockerfile') {
// For Docker Compose and Dockerfile, create an empty .env file even if there are no build-time variables
// This ensures the file exists when referenced in build commands
@ -2264,7 +2273,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
$this->nixpacks_plan_json = collect($parsed);
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
if (isDev()) {
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
} else {
$parsedForLog = $parsed;
unset($parsedForLog['variables']); // remove variables section to avoid exposing ENVs in production logs
$this->application_deployment_queue->addLogEntry('Final Nixpacks plan: '.json_encode($parsedForLog, JSON_PRETTY_PRINT), hidden: true);
}
if ($this->nixpacks_type === 'rust') {
// temporary: disable healthcheck for rust because the start phase does not have curl/wget
$this->application->health_check_enabled = false;

View file

@ -47,7 +47,7 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
match ($this->status) {
ProcessStatus::QUEUED => $this->body = "The preview deployment for **{$serviceName}** is queued. ⏳\n\n",
ProcessStatus::IN_PROGRESS => $this->body = "The preview deployment for **{$serviceName}** is in progress. 🟡\n\n",
ProcessStatus::FINISHED => $this->body = "The preview deployment for **{$serviceName}** is ready. 🟢\n\n".($this->preview->fqdn ? "[Open Preview]({$this->preview->fqdn}) | " : ''),
ProcessStatus::FINISHED => $this->body = "The preview deployment for **{$serviceName}** is ready. 🟢\n\n".$this->getPreviewLinks(),
ProcessStatus::ERROR => $this->body = "The preview deployment for **{$serviceName}** failed. 🔴\n\n",
ProcessStatus::KILLED => $this->body = "The preview deployment for **{$serviceName}** was killed. ⚫\n\n",
ProcessStatus::CANCELLED => $this->body = "The preview deployment for **{$serviceName}** was cancelled. 🚫\n\n",
@ -91,4 +91,27 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
{
githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete');
}
private function getPreviewLinks(): string
{
if ($this->application->build_pack === 'dockercompose') {
$dockerComposeDomains = json_decode($this->preview->docker_compose_domains, true) ?? [];
$links = [];
foreach ($dockerComposeDomains as $serviceName => $config) {
$domain = data_get($config, 'domain');
if (! empty($domain)) {
$firstDomain = str($domain)->explode(',')->first();
$firstDomain = trim($firstDomain);
if (! empty($firstDomain)) {
$links[] = "[Open {$serviceName}]({$firstDomain})";
}
}
}
return ! empty($links) ? implode(' | ', $links).' | ' : '';
}
return $this->preview->fqdn ? "[Open Preview]({$this->preview->fqdn}) | " : '';
}
}

View file

@ -22,6 +22,36 @@ class SendMessageToSlackJob implements ShouldQueue
}
public function handle(): void
{
if ($this->isSlackWebhook()) {
$this->sendToSlack();
return;
}
/**
* This works with Mattermost and as a fallback also with Slack, the notifications just look slightly different and advanced formatting for slack is not supported with Mattermost.
*
* @see https://github.com/coollabsio/coolify/pull/6139#issuecomment-3756777708
*/
$this->sendToMattermost();
}
private function isSlackWebhook(): bool
{
$parsedUrl = parse_url($this->webhookUrl);
if ($parsedUrl === false) {
return false;
}
$scheme = $parsedUrl['scheme'] ?? '';
$host = $parsedUrl['host'] ?? '';
return $scheme === 'https' && $host === 'hooks.slack.com';
}
private function sendToSlack(): void
{
Http::post($this->webhookUrl, [
'text' => $this->message->title,
@ -57,4 +87,24 @@ class SendMessageToSlackJob implements ShouldQueue
],
]);
}
/**
* @todo v5 refactor: Extract this into a separate SendMessageToMattermostJob.php triggered via the "mattermost" notification channel type.
*/
private function sendToMattermost(): void
{
$username = config('app.name');
Http::post($this->webhookUrl, [
'username' => $username,
'attachments' => [
[
'title' => $this->message->title,
'color' => $this->message->color,
'text' => $this->message->description,
'footer' => $username,
],
],
]);
}
}

View file

@ -33,6 +33,10 @@ class Index extends Component
public Collection $services;
public Collection $allProjects;
public Collection $allEnvironments;
public array $parameters;
public function mount()
@ -50,6 +54,33 @@ class Index extends Component
->firstOrFail();
$this->project = $project;
// Load projects and environments for breadcrumb navigation (avoids inline queries in view)
$this->allProjects = Project::ownedByCurrentTeamCached();
$this->allEnvironments = $project->environments()
->with([
'applications.additional_servers',
'applications.destination.server',
'services',
'services.destination.server',
'postgresqls',
'postgresqls.destination.server',
'redis',
'redis.destination.server',
'mongodbs',
'mongodbs.destination.server',
'mysqls',
'mysqls.destination.server',
'mariadbs',
'mariadbs.destination.server',
'keydbs',
'keydbs.destination.server',
'dragonflies',
'dragonflies.destination.server',
'clickhouses',
'clickhouses.destination.server',
])->get();
$this->environment = $environment->loadCount([
'applications',
'redis',
@ -71,11 +102,13 @@ class Index extends Component
'destination.server.settings',
'settings',
])->get()->sortBy('name');
$this->applications = $this->applications->map(function ($application) {
$projectUuid = $this->project->uuid;
$environmentUuid = $this->environment->uuid;
$this->applications = $this->applications->map(function ($application) use ($projectUuid, $environmentUuid) {
$application->hrefLink = route('project.application.configuration', [
'project_uuid' => data_get($application, 'environment.project.uuid'),
'environment_uuid' => data_get($application, 'environment.uuid'),
'application_uuid' => data_get($application, 'uuid'),
'project_uuid' => $projectUuid,
'environment_uuid' => $environmentUuid,
'application_uuid' => $application->uuid,
]);
return $application;
@ -98,11 +131,11 @@ class Index extends Component
'tags',
'destination.server.settings',
])->get()->sortBy('name');
$this->{$property} = $this->{$property}->map(function ($db) {
$this->{$property} = $this->{$property}->map(function ($db) use ($projectUuid, $environmentUuid) {
$db->hrefLink = route('project.database.configuration', [
'project_uuid' => $this->project->uuid,
'project_uuid' => $projectUuid,
'database_uuid' => $db->uuid,
'environment_uuid' => data_get($this->environment, 'uuid'),
'environment_uuid' => $environmentUuid,
]);
return $db;
@ -114,11 +147,11 @@ class Index extends Component
'tags',
'destination.server.settings',
])->get()->sortBy('name');
$this->services = $this->services->map(function ($service) {
$this->services = $this->services->map(function ($service) use ($projectUuid, $environmentUuid) {
$service->hrefLink = route('project.service.configuration', [
'project_uuid' => data_get($service, 'environment.project.uuid'),
'environment_uuid' => data_get($service, 'environment.uuid'),
'service_uuid' => data_get($service, 'uuid'),
'project_uuid' => $projectUuid,
'environment_uuid' => $environmentUuid,
'service_uuid' => $service->uuid,
]);
return $service;

View file

@ -1659,7 +1659,7 @@ class Application extends BaseModel
$this->custom_labels = base64_encode($customLabels);
}
$customLabels = base64_decode($this->custom_labels);
if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
if (mb_detect_encoding($customLabels, 'UTF-8', true) === false) {
$customLabels = str(implode('|coolify|', generateLabelsApplication($this, $preview)))->replace('|coolify|', "\n");
}
$this->custom_labels = base64_encode($customLabels);

View file

@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Once;
use Spatie\Url\Url;
class InstanceSettings extends Model
@ -35,6 +36,9 @@ class InstanceSettings extends Model
protected static function booted(): void
{
static::updated(function ($settings) {
// Clear once() cache so subsequent calls get fresh data
Once::flush();
// Clear trusted hosts cache when FQDN changes
if ($settings->wasChanged('fqdn')) {
\Cache::forget('instance_settings_fqdn_host');
@ -82,7 +86,7 @@ class InstanceSettings extends Model
public static function get()
{
return InstanceSettings::findOrFail(0);
return once(fn () => InstanceSettings::findOrFail(0));
}
// public function getRecipients($notification)

View file

@ -108,6 +108,12 @@ class Server extends BaseModel
public static $batch_counter = 0;
/**
* Identity map cache for request-scoped Server lookups.
* Prevents N+1 queries when the same Server is accessed multiple times.
*/
private static ?array $identityMapCache = null;
protected $appends = ['is_coolify_host'];
protected static function booted()
@ -186,6 +192,40 @@ class Server extends BaseModel
$server->settings()->delete();
$server->sslCertificates()->delete();
});
static::updated(function () {
static::flushIdentityMap();
});
}
/**
* Find a Server by ID using the identity map cache.
* This prevents N+1 queries when the same Server is accessed multiple times.
*/
public static function findCached($id): ?static
{
if ($id === null) {
return null;
}
if (static::$identityMapCache === null) {
static::$identityMapCache = [];
}
if (! isset(static::$identityMapCache[$id])) {
static::$identityMapCache[$id] = static::query()->find($id);
}
return static::$identityMapCache[$id];
}
/**
* Flush the identity map cache.
* Called automatically on update, and should be called in tests.
*/
public static function flushIdentityMap(): void
{
static::$identityMapCache = null;
}
protected $casts = [

View file

@ -869,6 +869,30 @@ class Service extends BaseModel
}
$fields->put('Meilisearch', $data->toArray());
break;
case $image->contains('linkding'):
$data = collect([]);
$SERVICE_USER_LINKDING = $this->environment_variables()->where('key', 'SERVICE_USER_LINKDING')->first();
$SERVICE_PASSWORD_LINKDING = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_LINKDING')->first();
if ($SERVICE_USER_LINKDING) {
$data = $data->merge([
'Superuser Name' => [
'key' => data_get($SERVICE_USER_LINKDING, 'key'),
'value' => data_get($SERVICE_USER_LINKDING, 'value'),
],
]);
}
if ($SERVICE_PASSWORD_LINKDING) {
$data = $data->merge([
'Superuser Password' => [
'key' => data_get($SERVICE_PASSWORD_LINKDING, 'key'),
'value' => data_get($SERVICE_PASSWORD_LINKDING, 'value'),
'isPassword' => true,
],
]);
}
$fields->put('Linkding', $data->toArray());
break;
case $image->contains('ghost'):
$data = collect([]);
$MAIL_OPTIONS_AUTH_PASS = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_PASS')->first();

View file

@ -73,6 +73,28 @@ class StandaloneDocker extends BaseModel
return $this->belongsTo(Server::class);
}
/**
* Get the server attribute using identity map caching.
* This intercepts lazy-loading to use cached Server lookups.
*/
public function getServerAttribute(): ?Server
{
// Use eager loaded data if available
if ($this->relationLoaded('server')) {
return $this->getRelation('server');
}
// Use identity map for lazy loading
$server = Server::findCached($this->server_id);
// Cache in relation for future access on this instance
if ($server) {
$this->setRelation('server', $server);
}
return $server;
}
public function services()
{
return $this->morphMany(Service::class, 'destination');

View file

@ -56,6 +56,28 @@ class SwarmDocker extends BaseModel
return $this->belongsTo(Server::class);
}
/**
* Get the server attribute using identity map caching.
* This intercepts lazy-loading to use cached Server lookups.
*/
public function getServerAttribute(): ?Server
{
// Use eager loaded data if available
if ($this->relationLoaded('server')) {
return $this->getRelation('server');
}
// Use identity map for lazy loading
$server = Server::findCached($this->server_id);
// Cache in relation for future access on this instance
if ($server) {
$this->setRelation('server', $server);
}
return $server;
}
public function services()
{
return $this->morphMany(Service::class, 'destination');

View file

@ -86,8 +86,11 @@ function sharedDataApplications()
'git_branch' => 'string',
'build_pack' => Rule::enum(BuildPackTypes::class),
'is_static' => 'boolean',
'is_spa' => 'boolean',
'is_auto_deploy_enabled' => 'boolean',
'is_force_https_enabled' => 'boolean',
'static_image' => Rule::enum(StaticImageTypes::class),
'domains' => 'string',
'domains' => 'string|nullable',
'redirect' => Rule::enum(RedirectTypes::class),
'git_commit_sha' => 'string',
'docker_registry_image_name' => 'string|nullable',
@ -129,9 +132,9 @@ function sharedDataApplications()
'manual_webhook_secret_gitlab' => 'string|nullable',
'manual_webhook_secret_bitbucket' => 'string|nullable',
'manual_webhook_secret_gitea' => 'string|nullable',
'dockerfile_location' => 'string|nullable',
'docker_compose_location' => 'string',
'docker_compose' => 'string|nullable',
'docker_compose_raw' => 'string|nullable',
'docker_compose_domains' => 'array|nullable',
'docker_compose_custom_start_command' => 'string|nullable',
'docker_compose_custom_build_command' => 'string|nullable',
@ -178,7 +181,12 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
$request->offsetUnset('private_key_uuid');
$request->offsetUnset('use_build_server');
$request->offsetUnset('is_static');
$request->offsetUnset('is_spa');
$request->offsetUnset('is_auto_deploy_enabled');
$request->offsetUnset('is_force_https_enabled');
$request->offsetUnset('connect_to_docker_network');
$request->offsetUnset('force_domain_override');
$request->offsetUnset('autogenerate_domain');
$request->offsetUnset('is_container_label_escape_enabled');
$request->offsetUnset('docker_compose_raw');
}

View file

@ -158,8 +158,7 @@ function checkIfDomainIsAlreadyUsedViaAPI(Collection|array $domains, ?string $te
return str($domain);
});
// Check applications within the same team
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid', 'name', 'id']);
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid', 'name', 'id', 'docker_compose_domains', 'build_pack']);
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->with('service:id,name')->get(['fqdn', 'uuid', 'id', 'service_id']);
if ($uuid) {
@ -168,23 +167,51 @@ function checkIfDomainIsAlreadyUsedViaAPI(Collection|array $domains, ?string $te
}
foreach ($applications as $app) {
if (is_null($app->fqdn)) {
continue;
}
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
if (! is_null($app->fqdn)) {
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
}
$naked_domain = str($domain)->value();
if ($domains->contains($naked_domain)) {
$conflicts[] = [
'domain' => $naked_domain,
'resource_name' => $app->name,
'resource_uuid' => $app->uuid,
'resource_type' => 'application',
'message' => "Domain $naked_domain is already in use by application '{$app->name}'",
];
}
}
$naked_domain = str($domain)->value();
if ($domains->contains($naked_domain)) {
$conflicts[] = [
'domain' => $naked_domain,
'resource_name' => $app->name,
'resource_uuid' => $app->uuid,
'resource_type' => 'application',
'message' => "Domain $naked_domain is already in use by application '{$app->name}'",
];
}
if ($app->build_pack === 'dockercompose' && ! empty($app->docker_compose_domains)) {
$dockerComposeDomains = json_decode($app->docker_compose_domains, true);
if (is_array($dockerComposeDomains)) {
foreach ($dockerComposeDomains as $serviceName => $domainConfig) {
$domainValue = data_get($domainConfig, 'domain');
if (empty($domainValue)) {
continue;
}
$list_of_domains = collect(explode(',', $domainValue))->filter(fn ($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
}
$naked_domain = str($domain)->value();
if ($domains->contains($naked_domain)) {
$conflicts[] = [
'domain' => $naked_domain,
'resource_name' => $app->name,
'resource_uuid' => $app->uuid,
'resource_type' => 'application',
'service_name' => $serviceName,
'message' => "Domain $naked_domain is already in use by application '{$app->name}' (service: {$serviceName})",
];
}
}
}
}
}
}

View file

@ -2,7 +2,7 @@
return [
'coolify' => [
'version' => '4.0.0-beta.461',
'version' => '4.0.0-beta.462',
'helper_version' => '1.0.12',
'realtime_version' => '1.0.10',
'self_hosted' => env('SELF_HOSTED', true),

View file

@ -135,7 +135,7 @@
},
"domains": {
"type": "string",
"description": "The application domains."
"description": "The application URLs in a comma-separated list."
},
"git_commit_sha": {
"type": "string",
@ -153,6 +153,18 @@
"type": "boolean",
"description": "The flag to indicate if the application is static."
},
"is_spa": {
"type": "boolean",
"description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true."
},
"is_auto_deploy_enabled": {
"type": "boolean",
"description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true."
},
"is_force_https_enabled": {
"type": "boolean",
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
},
"static_image": {
"type": "string",
"enum": [
@ -322,14 +334,14 @@
"type": "string",
"description": "The Dockerfile content."
},
"dockerfile_location": {
"type": "string",
"description": "The Dockerfile location in the repository."
},
"docker_compose_location": {
"type": "string",
"description": "The Docker Compose location."
},
"docker_compose_raw": {
"type": "string",
"description": "The Docker Compose raw content."
},
"docker_compose_custom_start_command": {
"type": "string",
"description": "The Docker Compose custom start command."
@ -340,7 +352,20 @@
},
"docker_compose_domains": {
"type": "array",
"description": "The Docker Compose domains."
"description": "Array of URLs to be applied to containers of a dockercompose application.",
"items": {
"properties": {
"name": {
"type": "string",
"description": "The service name as defined in docker-compose."
},
"domain": {
"type": "string",
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
}
},
"type": "object"
}
},
"watch_paths": {
"type": "string",
@ -555,7 +580,7 @@
},
"domains": {
"type": "string",
"description": "The application domains."
"description": "The application URLs in a comma-separated list."
},
"git_commit_sha": {
"type": "string",
@ -573,6 +598,18 @@
"type": "boolean",
"description": "The flag to indicate if the application is static."
},
"is_spa": {
"type": "boolean",
"description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true."
},
"is_auto_deploy_enabled": {
"type": "boolean",
"description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true."
},
"is_force_https_enabled": {
"type": "boolean",
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
},
"static_image": {
"type": "string",
"enum": [
@ -742,14 +779,14 @@
"type": "string",
"description": "The Dockerfile content."
},
"dockerfile_location": {
"type": "string",
"description": "The Dockerfile location in the repository"
},
"docker_compose_location": {
"type": "string",
"description": "The Docker Compose location."
},
"docker_compose_raw": {
"type": "string",
"description": "The Docker Compose raw content."
},
"docker_compose_custom_start_command": {
"type": "string",
"description": "The Docker Compose custom start command."
@ -760,7 +797,20 @@
},
"docker_compose_domains": {
"type": "array",
"description": "The Docker Compose domains."
"description": "Array of URLs to be applied to containers of a dockercompose application.",
"items": {
"properties": {
"name": {
"type": "string",
"description": "The service name as defined in docker-compose."
},
"domain": {
"type": "string",
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
}
},
"type": "object"
}
},
"watch_paths": {
"type": "string",
@ -975,7 +1025,7 @@
},
"domains": {
"type": "string",
"description": "The application domains."
"description": "The application URLs in a comma-separated list."
},
"git_commit_sha": {
"type": "string",
@ -993,6 +1043,18 @@
"type": "boolean",
"description": "The flag to indicate if the application is static."
},
"is_spa": {
"type": "boolean",
"description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true."
},
"is_auto_deploy_enabled": {
"type": "boolean",
"description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true."
},
"is_force_https_enabled": {
"type": "boolean",
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
},
"static_image": {
"type": "string",
"enum": [
@ -1162,14 +1224,14 @@
"type": "string",
"description": "The Dockerfile content."
},
"dockerfile_location": {
"type": "string",
"description": "The Dockerfile location in the repository."
},
"docker_compose_location": {
"type": "string",
"description": "The Docker Compose location."
},
"docker_compose_raw": {
"type": "string",
"description": "The Docker Compose raw content."
},
"docker_compose_custom_start_command": {
"type": "string",
"description": "The Docker Compose custom start command."
@ -1180,7 +1242,20 @@
},
"docker_compose_domains": {
"type": "array",
"description": "The Docker Compose domains."
"description": "Array of URLs to be applied to containers of a dockercompose application.",
"items": {
"properties": {
"name": {
"type": "string",
"description": "The service name as defined in docker-compose."
},
"domain": {
"type": "string",
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
}
},
"type": "object"
}
},
"watch_paths": {
"type": "string",
@ -1318,8 +1393,8 @@
"tags": [
"Applications"
],
"summary": "Create (Dockerfile)",
"description": "Create new application based on a simple Dockerfile.",
"summary": "Create (Dockerfile without git)",
"description": "Create new application based on a simple Dockerfile (without git).",
"operationId": "create-dockerfile-application",
"requestBody": {
"description": "Application object that needs to be created.",
@ -1383,7 +1458,7 @@
},
"domains": {
"type": "string",
"description": "The application domains."
"description": "The application URLs in a comma-separated list."
},
"docker_registry_image_name": {
"type": "string",
@ -1535,6 +1610,10 @@
"type": "boolean",
"description": "The flag to indicate if the application should be deployed instantly."
},
"is_force_https_enabled": {
"type": "boolean",
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
},
"use_build_server": {
"type": "boolean",
"nullable": true,
@ -1667,8 +1746,8 @@
"tags": [
"Applications"
],
"summary": "Create (Docker Image)",
"description": "Create new application based on a prebuilt docker image",
"summary": "Create (Docker Image without git)",
"description": "Create new application based on a prebuilt docker image (without git).",
"operationId": "create-dockerimage-application",
"requestBody": {
"description": "Application object that needs to be created.",
@ -1727,7 +1806,7 @@
},
"domains": {
"type": "string",
"description": "The application domains."
"description": "The application URLs in a comma-separated list."
},
"ports_mappings": {
"type": "string",
@ -1867,6 +1946,10 @@
"type": "boolean",
"description": "The flag to indicate if the application should be deployed instantly."
},
"is_force_https_enabled": {
"type": "boolean",
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
},
"use_build_server": {
"type": "boolean",
"nullable": true,
@ -2000,7 +2083,7 @@
"Applications"
],
"summary": "Create (Docker Compose)",
"description": "Create new application based on a docker-compose file.",
"description": "Deprecated: Use POST \/api\/v1\/services instead.",
"operationId": "create-dockercompose-application",
"requestBody": {
"description": "Application object that needs to be created.",
@ -2153,6 +2236,7 @@
}
}
},
"deprecated": true,
"security": [
{
"bearerAuth": []
@ -2374,7 +2458,7 @@
},
"domains": {
"type": "string",
"description": "The application domains."
"description": "The application URLs in a comma-separated list."
},
"git_commit_sha": {
"type": "string",
@ -2392,6 +2476,18 @@
"type": "boolean",
"description": "The flag to indicate if the application is static."
},
"is_spa": {
"type": "boolean",
"description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true."
},
"is_auto_deploy_enabled": {
"type": "boolean",
"description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true."
},
"is_force_https_enabled": {
"type": "boolean",
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
},
"install_command": {
"type": "string",
"description": "The install command."
@ -2554,14 +2650,14 @@
"type": "string",
"description": "The Dockerfile content."
},
"dockerfile_location": {
"type": "string",
"description": "The Dockerfile location in the repository."
},
"docker_compose_location": {
"type": "string",
"description": "The Docker Compose location."
},
"docker_compose_raw": {
"type": "string",
"description": "The Docker Compose raw content."
},
"docker_compose_custom_start_command": {
"type": "string",
"description": "The Docker Compose custom start command."
@ -2572,7 +2668,20 @@
},
"docker_compose_domains": {
"type": "array",
"description": "The Docker Compose domains."
"description": "Array of URLs to be applied to containers of a dockercompose application.",
"items": {
"properties": {
"name": {
"type": "string",
"description": "The service name as defined in docker-compose."
},
"domain": {
"type": "string",
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
}
},
"type": "object"
}
},
"watch_paths": {
"type": "string",
@ -8809,96 +8918,8 @@
],
"properties": {
"type": {
"description": "The one-click service type",
"type": "string",
"enum": [
"activepieces",
"appsmith",
"appwrite",
"authentik",
"babybuddy",
"budge",
"changedetection",
"chatwoot",
"classicpress-with-mariadb",
"classicpress-with-mysql",
"classicpress-without-database",
"cloudflared",
"code-server",
"dashboard",
"directus",
"directus-with-postgresql",
"docker-registry",
"docuseal",
"docuseal-with-postgres",
"dokuwiki",
"duplicati",
"emby",
"embystat",
"fider",
"filebrowser",
"firefly",
"formbricks",
"ghost",
"gitea",
"gitea-with-mariadb",
"gitea-with-mysql",
"gitea-with-postgresql",
"glance",
"glances",
"glitchtip",
"grafana",
"grafana-with-postgresql",
"grocy",
"heimdall",
"homepage",
"jellyfin",
"kuzzle",
"listmonk",
"logto",
"mediawiki",
"meilisearch",
"metabase",
"metube",
"minio",
"moodle",
"n8n",
"n8n-with-postgresql",
"next-image-transformation",
"nextcloud",
"nocodb",
"odoo",
"openblocks",
"pairdrop",
"penpot",
"phpmyadmin",
"pocketbase",
"posthog",
"reactive-resume",
"rocketchat",
"shlink",
"slash",
"snapdrop",
"statusnook",
"stirling-pdf",
"supabase",
"syncthing",
"tolgee",
"trigger",
"trigger-with-external-database",
"twenty",
"umami",
"unleash-with-postgresql",
"unleash-without-database",
"uptime-kuma",
"vaultwarden",
"vikunja",
"weblate",
"whoogle",
"wordpress-with-mariadb",
"wordpress-with-mysql",
"wordpress-without-database"
]
"description": "The one-click service type (e.g. \"actualbudget\", \"calibre-web\", \"gitea-with-mysql\" ...)",
"type": "string"
},
"name": {
"type": "string",
@ -8937,7 +8958,29 @@
},
"docker_compose_raw": {
"type": "string",
"description": "The Docker Compose raw content."
"description": "The base64 encoded Docker Compose content."
},
"urls": {
"type": "array",
"description": "Array of URLs to be applied to containers of a service.",
"items": {
"properties": {
"name": {
"type": "string",
"description": "The service name as defined in docker-compose."
},
"url": {
"type": "string",
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")."
}
},
"type": "object"
}
},
"force_domain_override": {
"type": "boolean",
"default": false,
"description": "Force domain override even if conflicts are detected."
}
},
"type": "object"
@ -8975,6 +9018,60 @@
"400": {
"$ref": "#\/components\/responses\/400"
},
"409": {
"description": "Domain conflicts detected.",
"content": {
"application\/json": {
"schema": {
"properties": {
"message": {
"type": "string",
"example": "Domain conflicts detected. Use force_domain_override=true to proceed."
},
"warning": {
"type": "string",
"example": "Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior."
},
"conflicts": {
"type": "array",
"items": {
"properties": {
"domain": {
"type": "string",
"example": "example.com"
},
"resource_name": {
"type": "string",
"example": "My Application"
},
"resource_uuid": {
"type": "string",
"nullable": true,
"example": "abc123-def456"
},
"resource_type": {
"type": "string",
"enum": [
"application",
"service",
"instance"
],
"example": "application"
},
"message": {
"type": "string",
"example": "Domain example.com is already in use by application 'My Application'"
}
},
"type": "object"
}
}
},
"type": "object"
}
}
}
},
"422": {
"$ref": "#\/components\/responses\/422"
}
@ -9187,7 +9284,29 @@
},
"docker_compose_raw": {
"type": "string",
"description": "The Docker Compose raw content."
"description": "The base64 encoded Docker Compose content."
},
"urls": {
"type": "array",
"description": "Array of URLs to be applied to containers of a service.",
"items": {
"properties": {
"name": {
"type": "string",
"description": "The service name as defined in docker-compose."
},
"url": {
"type": "string",
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")."
}
},
"type": "object"
}
},
"force_domain_override": {
"type": "boolean",
"default": false,
"description": "Force domain override even if conflicts are detected."
}
},
"type": "object"
@ -9228,6 +9347,60 @@
"404": {
"$ref": "#\/components\/responses\/404"
},
"409": {
"description": "Domain conflicts detected.",
"content": {
"application\/json": {
"schema": {
"properties": {
"message": {
"type": "string",
"example": "Domain conflicts detected. Use force_domain_override=true to proceed."
},
"warning": {
"type": "string",
"example": "Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior."
},
"conflicts": {
"type": "array",
"items": {
"properties": {
"domain": {
"type": "string",
"example": "example.com"
},
"resource_name": {
"type": "string",
"example": "My Application"
},
"resource_uuid": {
"type": "string",
"nullable": true,
"example": "abc123-def456"
},
"resource_type": {
"type": "string",
"enum": [
"application",
"service",
"instance"
],
"example": "application"
},
"message": {
"type": "string",
"example": "Domain example.com is already in use by application 'My Application'"
}
},
"type": "object"
}
}
},
"type": "object"
}
}
}
},
"422": {
"$ref": "#\/components\/responses\/422"
}

View file

@ -97,7 +97,7 @@ paths:
description: 'The application description.'
domains:
type: string
description: 'The application domains.'
description: 'The application URLs in a comma-separated list.'
git_commit_sha:
type: string
description: 'The git commit SHA.'
@ -110,6 +110,15 @@ paths:
is_static:
type: boolean
description: 'The flag to indicate if the application is static.'
is_spa:
type: boolean
description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'
is_auto_deploy_enabled:
type: boolean
description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'
is_force_https_enabled:
type: boolean
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
static_image:
type: string
enum: ['nginx:alpine']
@ -234,12 +243,12 @@ paths:
dockerfile:
type: string
description: 'The Dockerfile content.'
dockerfile_location:
type: string
description: 'The Dockerfile location in the repository.'
docker_compose_location:
type: string
description: 'The Docker Compose location.'
docker_compose_raw:
type: string
description: 'The Docker Compose raw content.'
docker_compose_custom_start_command:
type: string
description: 'The Docker Compose custom start command.'
@ -248,7 +257,8 @@ paths:
description: 'The Docker Compose custom build command.'
docker_compose_domains:
type: array
description: 'The Docker Compose domains.'
description: 'Array of URLs to be applied to containers of a dockercompose application.'
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")' } }, type: object }
watch_paths:
type: string
description: 'The watch paths.'
@ -371,7 +381,7 @@ paths:
description: 'The application description.'
domains:
type: string
description: 'The application domains.'
description: 'The application URLs in a comma-separated list.'
git_commit_sha:
type: string
description: 'The git commit SHA.'
@ -384,6 +394,15 @@ paths:
is_static:
type: boolean
description: 'The flag to indicate if the application is static.'
is_spa:
type: boolean
description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'
is_auto_deploy_enabled:
type: boolean
description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'
is_force_https_enabled:
type: boolean
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
static_image:
type: string
enum: ['nginx:alpine']
@ -508,12 +527,12 @@ paths:
dockerfile:
type: string
description: 'The Dockerfile content.'
dockerfile_location:
type: string
description: 'The Dockerfile location in the repository'
docker_compose_location:
type: string
description: 'The Docker Compose location.'
docker_compose_raw:
type: string
description: 'The Docker Compose raw content.'
docker_compose_custom_start_command:
type: string
description: 'The Docker Compose custom start command.'
@ -522,7 +541,8 @@ paths:
description: 'The Docker Compose custom build command.'
docker_compose_domains:
type: array
description: 'The Docker Compose domains.'
description: 'Array of URLs to be applied to containers of a dockercompose application.'
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")' } }, type: object }
watch_paths:
type: string
description: 'The watch paths.'
@ -645,7 +665,7 @@ paths:
description: 'The application description.'
domains:
type: string
description: 'The application domains.'
description: 'The application URLs in a comma-separated list.'
git_commit_sha:
type: string
description: 'The git commit SHA.'
@ -658,6 +678,15 @@ paths:
is_static:
type: boolean
description: 'The flag to indicate if the application is static.'
is_spa:
type: boolean
description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'
is_auto_deploy_enabled:
type: boolean
description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'
is_force_https_enabled:
type: boolean
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
static_image:
type: string
enum: ['nginx:alpine']
@ -782,12 +811,12 @@ paths:
dockerfile:
type: string
description: 'The Dockerfile content.'
dockerfile_location:
type: string
description: 'The Dockerfile location in the repository.'
docker_compose_location:
type: string
description: 'The Docker Compose location.'
docker_compose_raw:
type: string
description: 'The Docker Compose raw content.'
docker_compose_custom_start_command:
type: string
description: 'The Docker Compose custom start command.'
@ -796,7 +825,8 @@ paths:
description: 'The Docker Compose custom build command.'
docker_compose_domains:
type: array
description: 'The Docker Compose domains.'
description: 'Array of URLs to be applied to containers of a dockercompose application.'
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")' } }, type: object }
watch_paths:
type: string
description: 'The watch paths.'
@ -860,8 +890,8 @@ paths:
post:
tags:
- Applications
summary: 'Create (Dockerfile)'
description: 'Create new application based on a simple Dockerfile.'
summary: 'Create (Dockerfile without git)'
description: 'Create new application based on a simple Dockerfile (without git).'
operationId: create-dockerfile-application
requestBody:
description: 'Application object that needs to be created.'
@ -909,7 +939,7 @@ paths:
description: 'The application description.'
domains:
type: string
description: 'The application domains.'
description: 'The application URLs in a comma-separated list.'
docker_registry_image_name:
type: string
description: 'The docker registry image name.'
@ -1021,6 +1051,9 @@ paths:
instant_deploy:
type: boolean
description: 'The flag to indicate if the application should be deployed instantly.'
is_force_https_enabled:
type: boolean
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
use_build_server:
type: boolean
nullable: true
@ -1081,8 +1114,8 @@ paths:
post:
tags:
- Applications
summary: 'Create (Docker Image)'
description: 'Create new application based on a prebuilt docker image'
summary: 'Create (Docker Image without git)'
description: 'Create new application based on a prebuilt docker image (without git).'
operationId: create-dockerimage-application
requestBody:
description: 'Application object that needs to be created.'
@ -1130,7 +1163,7 @@ paths:
description: 'The application description.'
domains:
type: string
description: 'The application domains.'
description: 'The application URLs in a comma-separated list.'
ports_mappings:
type: string
description: 'The ports mappings.'
@ -1233,6 +1266,9 @@ paths:
instant_deploy:
type: boolean
description: 'The flag to indicate if the application should be deployed instantly.'
is_force_https_enabled:
type: boolean
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
use_build_server:
type: boolean
nullable: true
@ -1294,7 +1330,7 @@ paths:
tags:
- Applications
summary: 'Create (Docker Compose)'
description: 'Create new application based on a docker-compose file.'
description: 'Deprecated: Use POST /api/v1/services instead.'
operationId: create-dockercompose-application
requestBody:
description: 'Application object that needs to be created.'
@ -1374,6 +1410,7 @@ paths:
warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }
conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } }
type: object
deprecated: true
security:
-
bearerAuth: []
@ -1529,7 +1566,7 @@ paths:
description: 'The application description.'
domains:
type: string
description: 'The application domains.'
description: 'The application URLs in a comma-separated list.'
git_commit_sha:
type: string
description: 'The git commit SHA.'
@ -1542,6 +1579,15 @@ paths:
is_static:
type: boolean
description: 'The flag to indicate if the application is static.'
is_spa:
type: boolean
description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'
is_auto_deploy_enabled:
type: boolean
description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'
is_force_https_enabled:
type: boolean
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
install_command:
type: string
description: 'The install command.'
@ -1662,12 +1708,12 @@ paths:
dockerfile:
type: string
description: 'The Dockerfile content.'
dockerfile_location:
type: string
description: 'The Dockerfile location in the repository.'
docker_compose_location:
type: string
description: 'The Docker Compose location.'
docker_compose_raw:
type: string
description: 'The Docker Compose raw content.'
docker_compose_custom_start_command:
type: string
description: 'The Docker Compose custom start command.'
@ -1676,7 +1722,8 @@ paths:
description: 'The Docker Compose custom build command.'
docker_compose_domains:
type: array
description: 'The Docker Compose domains.'
description: 'Array of URLs to be applied to containers of a dockercompose application.'
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")' } }, type: object }
watch_paths:
type: string
description: 'The watch paths.'
@ -5581,9 +5628,8 @@ paths:
- environment_uuid
properties:
type:
description: 'The one-click service type'
description: 'The one-click service type (e.g. "actualbudget", "calibre-web", "gitea-with-mysql" ...)'
type: string
enum: [activepieces, appsmith, appwrite, authentik, babybuddy, budge, changedetection, chatwoot, classicpress-with-mariadb, classicpress-with-mysql, classicpress-without-database, cloudflared, code-server, dashboard, directus, directus-with-postgresql, docker-registry, docuseal, docuseal-with-postgres, dokuwiki, duplicati, emby, embystat, fider, filebrowser, firefly, formbricks, ghost, gitea, gitea-with-mariadb, gitea-with-mysql, gitea-with-postgresql, glance, glances, glitchtip, grafana, grafana-with-postgresql, grocy, heimdall, homepage, jellyfin, kuzzle, listmonk, logto, mediawiki, meilisearch, metabase, metube, minio, moodle, n8n, n8n-with-postgresql, next-image-transformation, nextcloud, nocodb, odoo, openblocks, pairdrop, penpot, phpmyadmin, pocketbase, posthog, reactive-resume, rocketchat, shlink, slash, snapdrop, statusnook, stirling-pdf, supabase, syncthing, tolgee, trigger, trigger-with-external-database, twenty, umami, unleash-with-postgresql, unleash-without-database, uptime-kuma, vaultwarden, vikunja, weblate, whoogle, wordpress-with-mariadb, wordpress-with-mysql, wordpress-without-database]
name:
type: string
maxLength: 255
@ -5613,7 +5659,15 @@ paths:
description: 'Start the service immediately after creation.'
docker_compose_raw:
type: string
description: 'The Docker Compose raw content.'
description: 'The base64 encoded Docker Compose content.'
urls:
type: array
description: 'Array of URLs to be applied to containers of a service.'
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object }
force_domain_override:
type: boolean
default: false
description: 'Force domain override even if conflicts are detected.'
type: object
responses:
'201':
@ -5629,6 +5683,16 @@ paths:
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'409':
description: 'Domain conflicts detected.'
content:
application/json:
schema:
properties:
message: { type: string, example: 'Domain conflicts detected. Use force_domain_override=true to proceed.' }
warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }
conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } }
type: object
'422':
$ref: '#/components/responses/422'
security:
@ -5780,7 +5844,15 @@ paths:
description: 'Connect the service to the predefined docker network.'
docker_compose_raw:
type: string
description: 'The Docker Compose raw content.'
description: 'The base64 encoded Docker Compose content.'
urls:
type: array
description: 'Array of URLs to be applied to containers of a service.'
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object }
force_domain_override:
type: boolean
default: false
description: 'Force domain override even if conflicts are detected.'
type: object
responses:
'200':
@ -5798,6 +5870,16 @@ paths:
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
'409':
description: 'Domain conflicts detected.'
content:
application/json:
schema:
properties:
message: { type: string, example: 'Domain conflicts detected. Use force_domain_override=true to proceed.' }
warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }
conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } }
type: object
'422':
$ref: '#/components/responses/422'
security:

View file

@ -1,10 +1,10 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.461"
"version": "4.0.0-beta.462"
},
"nightly": {
"version": "4.0.0-beta.462"
"version": "4.0.0-beta.463"
},
"helper": {
"version": "1.0.12"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

90
public/svgs/chibisafe.svg Normal file
View file

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="234px" height="245px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
<g><path style="opacity:1" fill="#1a1d20" d="M 57.5,-0.5 C 58.5,-0.5 59.5,-0.5 60.5,-0.5C 63.806,3.91646 67.4727,8.08312 71.5,12C 86.178,12.8328 100.845,12.6662 115.5,11.5C 114.484,8.86628 114.484,6.19962 115.5,3.5C 135.181,2.0004 154.848,2.16707 174.5,4C 175.833,6.66667 175.833,9.33333 174.5,12C 188.266,10.3377 200.1,14.171 210,23.5C 213.73,28.2277 216.064,33.561 217,39.5C 217.333,49.5 217.667,59.5 218,69.5C 221.004,71.3392 223.504,73.6726 225.5,76.5C 223.621,80.5532 221.121,84.2199 218,87.5C 217.333,104.833 217.333,122.167 218,139.5C 223.106,143.63 228.273,147.63 233.5,151.5C 233.5,152.5 233.5,153.5 233.5,154.5C 228.352,160.763 223.186,167.096 218,173.5C 217.667,184.833 217.333,196.167 217,207.5C 213.819,223.181 204.319,232.181 188.5,234.5C 189.045,237.998 188.212,240.998 186,243.5C 177.42,243.097 168.753,243.097 160,243.5C 158.931,240.792 157.764,238.125 156.5,235.5C 130.506,234.333 104.506,234.167 78.5,235C 77.3785,238.07 75.7118,240.737 73.5,243C 64.392,243.122 55.5587,243.289 47,243.5C 46.502,240.518 46.3354,237.518 46.5,234.5C 31.9392,233.061 22.4392,225.061 18,210.5C 17.8224,152.987 17.3224,95.8201 16.5,39C 19.8683,35.0885 20.2017,30.9219 17.5,26.5C 31.5531,18.4804 44.8865,9.4804 57.5,-0.5 Z"/></g>
<g><path style="opacity:1" fill="#0f4a8c" d="M 144.5,8.5 C 144.5,8.83333 144.5,9.16667 144.5,9.5C 143.833,9.5 143.167,9.5 142.5,9.5C 138.609,10.7274 136.609,13.394 136.5,17.5C 135.07,17.5792 133.736,17.2458 132.5,16.5C 129.807,19.65 127.474,22.9833 125.5,26.5C 123.517,26.8432 122.183,26.1765 121.5,24.5C 124.093,19.2421 126.426,14.5754 128.5,10.5C 129.738,10.19 129.738,9.68999 128.5,9C 132.887,8.77398 137.221,8.27398 141.5,7.5C 142.417,8.12723 143.417,8.46056 144.5,8.5 Z"/></g>
<g><path style="opacity:1" fill="#8bc5ed" d="M 128.5,10.5 C 126.426,14.5754 124.093,19.2421 121.5,24.5C 120.512,19.3595 120.179,14.0262 120.5,8.5C 123.533,8.13152 126.2,8.79818 128.5,10.5 Z"/></g>
<g><path style="opacity:1" fill="#93cdf6" d="M 144.5,9.5 C 144.5,9.16667 144.5,8.83333 144.5,8.5C 146.5,8.5 148.5,8.5 150.5,8.5C 154.953,8.56209 159.619,8.72876 164.5,9C 162.019,12.8057 159.685,16.6391 157.5,20.5C 157.167,20.5 156.833,20.5 156.5,20.5C 156.904,17.9896 156.404,15.3229 155,12.5C 154.424,15.1242 153.09,15.7908 151,14.5C 150.51,15.7932 150.343,17.1266 150.5,18.5C 149.997,23.5 149.664,23.1667 149.5,17.5C 149.65,13.5301 147.983,10.8635 144.5,9.5 Z"/></g>
<g><path style="opacity:1" fill="#c08772" d="M 64.5,12.5 C 63.9035,13.9354 63.9035,15.602 64.5,17.5C 63.8333,17.5 63.1667,17.5 62.5,17.5C 61.7387,16.6091 60.7387,15.9424 59.5,15.5C 58.537,15.9698 58.537,16.6365 59.5,17.5C 57.8793,17.7869 56.8793,18.7869 56.5,20.5C 57.4415,20.4895 58.1081,20.9895 58.5,22C 58.0109,23.5637 57.6776,25.0637 57.5,26.5C 56.8333,26.5 56.1667,26.5 55.5,26.5C 55.7989,24.396 55.4656,22.396 54.5,20.5C 53.2638,21.2458 51.9305,21.5792 50.5,21.5C 50.6427,19.7376 50.3094,18.0709 49.5,16.5C 51.9288,15.1187 51.5954,14.1187 48.5,13.5C 48.8333,12.5 49.5,11.8333 50.5,11.5C 52.9386,13.1647 54.4386,15.4981 55,18.5C 58.5266,14.5423 57.6932,11.8757 52.5,10.5C 54.4293,8.85176 56.4293,7.18509 58.5,5.5C 59.2421,7.10822 59.4087,8.77489 59,10.5C 61.301,8.47545 63.1343,9.14212 64.5,12.5 Z"/></g>
<g><path style="opacity:1" fill="#91b0cb" d="M 142.5,9.5 C 143.167,9.5 143.833,9.5 144.5,9.5C 147.983,10.8635 149.65,13.5301 149.5,17.5C 147.695,16.7629 146.029,15.7629 144.5,14.5C 143.294,16.4841 143.294,18.4841 144.5,20.5C 144.043,21.2975 143.376,21.6309 142.5,21.5C 141.833,20.8333 141.167,20.1667 140.5,19.5C 139.508,19.3284 138.842,19.6618 138.5,20.5C 137.441,18.274 137.608,15.9406 139,13.5C 141.071,12.8971 142.238,11.5638 142.5,9.5 Z"/></g>
<g><path style="opacity:1" fill="#8ecaf3" d="M 169.5,8.5 C 169.5,14.5 169.5,20.5 169.5,26.5C 161.568,27.787 160.568,25.9537 166.5,21C 166.167,20.8333 165.833,20.6667 165.5,20.5C 163.493,22.5332 161.493,24.5332 159.5,26.5C 159.5,26.1667 159.5,25.8333 159.5,25.5C 159.34,23.8008 159.506,22.1341 160,20.5C 162.872,16.8175 165.038,12.8175 166.5,8.5C 167.5,8.5 168.5,8.5 169.5,8.5 Z"/></g>
<g><path style="opacity:1" fill="#eeda92" d="M 52.5,10.5 C 57.6932,11.8757 58.5266,14.5423 55,18.5C 54.4386,15.4981 52.9386,13.1647 50.5,11.5C 51.1667,11.1667 51.8333,10.8333 52.5,10.5 Z"/></g>
<g><path style="opacity:1" fill="#325576" d="M 169.5,8.5 C 168.5,8.5 167.5,8.5 166.5,8.5C 165.038,12.8175 162.872,16.8175 160,20.5C 159.506,22.1341 159.34,23.8008 159.5,25.5C 158.711,25.2172 158.044,24.7172 157.5,24C 158.684,22.8545 158.684,21.6879 157.5,20.5C 159.685,16.6391 162.019,12.8057 164.5,9C 159.619,8.72876 154.953,8.56209 150.5,8.5C 156.932,7.19274 163.266,7.19274 169.5,8.5 Z"/></g>
<g><path style="opacity:1" fill="#efd1c2" d="M 48.5,13.5 C 51.5954,14.1187 51.9288,15.1187 49.5,16.5C 50.3094,18.0709 50.6427,19.7376 50.5,21.5C 51.9305,21.5792 53.2638,21.2458 54.5,20.5C 55.4656,22.396 55.7989,24.396 55.5,26.5C 54.1667,26.5 52.8333,26.5 51.5,26.5C 52.8499,25.1765 52.6833,24.1765 51,23.5C 49.2509,23.7483 48.4176,24.7483 48.5,26.5C 46.8333,26.5 45.1667,26.5 43.5,26.5C 43.5111,24.4534 43.8444,22.4534 44.5,20.5C 44.1667,19.8333 43.8333,19.1667 43.5,18.5C 42.8182,19.2427 42.1515,19.9094 41.5,20.5C 41.5,19.8333 41.5,19.1667 41.5,18.5C 43.5313,16.4776 45.8646,14.8109 48.5,13.5 Z"/></g>
<g><path style="opacity:1" fill="#7f91a8" d="M 156.5,20.5 C 155.055,22.0627 153.389,23.396 151.5,24.5C 151.247,22.1727 151.914,20.1727 153.5,18.5C 152.451,15.9548 151.451,15.9548 150.5,18.5C 150.343,17.1266 150.51,15.7932 151,14.5C 153.09,15.7908 154.424,15.1242 155,12.5C 156.404,15.3229 156.904,17.9896 156.5,20.5 Z"/></g>
<g><path style="opacity:1" fill="#362e2e" d="M 142.5,9.5 C 142.238,11.5638 141.071,12.8971 139,13.5C 137.608,15.9406 137.441,18.274 138.5,20.5C 138.573,22.0269 137.906,23.0269 136.5,23.5C 136.5,22.5 136.5,21.5 136.5,20.5C 136.5,19.5 136.5,18.5 136.5,17.5C 136.609,13.394 138.609,10.7274 142.5,9.5 Z"/></g>
<g><path style="opacity:1" fill="#1d1d1f" d="M 62.5,17.5 C 62.5,18.5 62.5,19.5 62.5,20.5C 61.5,19.5 60.5,18.5 59.5,17.5C 58.537,16.6365 58.537,15.9698 59.5,15.5C 60.7387,15.9424 61.7387,16.6091 62.5,17.5 Z"/></g>
<g><path style="opacity:1" fill="#d6c2bd" d="M 64.5,12.5 C 66.5,13.8333 68.1667,15.5 69.5,17.5C 68.3226,19.7843 66.6559,20.1177 64.5,18.5C 64.5,18.1667 64.5,17.8333 64.5,17.5C 63.9035,15.602 63.9035,13.9354 64.5,12.5 Z"/></g>
<g><path style="opacity:1" fill="#7fb0d4" d="M 136.5,17.5 C 136.5,18.5 136.5,19.5 136.5,20.5C 134.692,20.4914 133.025,20.9914 131.5,22C 129.957,23.7392 128.29,25.2392 126.5,26.5C 126.167,26.5 125.833,26.5 125.5,26.5C 127.474,22.9833 129.807,19.65 132.5,16.5C 133.736,17.2458 135.07,17.5792 136.5,17.5 Z"/></g>
<g><path style="opacity:1" fill="#e8d891" d="M 41.5,18.5 C 41.5,19.1667 41.5,19.8333 41.5,20.5C 41.719,21.675 41.3857,22.675 40.5,23.5C 39.4728,22.2086 38.4728,22.2086 37.5,23.5C 37.5,22.5 37.5,21.5 37.5,20.5C 38.6049,19.3995 39.9382,18.7329 41.5,18.5 Z"/></g>
<g><path style="opacity:1" fill="#67584c" d="M 69.5,17.5 C 70.4013,18.2905 71.0679,19.2905 71.5,20.5C 71.8072,22.4458 71.8072,24.1125 71.5,25.5C 69.9309,24.286 69.0976,22.6193 69,20.5C 68.2742,23.285 66.7742,23.9516 64.5,22.5C 64.5,21.1667 64.5,19.8333 64.5,18.5C 66.6559,20.1177 68.3226,19.7843 69.5,17.5 Z"/></g>
<g><path style="opacity:1" fill="#eddb61" d="M 62.5,17.5 C 63.1667,17.5 63.8333,17.5 64.5,17.5C 64.5,17.8333 64.5,18.1667 64.5,18.5C 64.5,19.8333 64.5,21.1667 64.5,22.5C 63.5,22.1667 62.8333,21.5 62.5,20.5C 62.5,19.5 62.5,18.5 62.5,17.5 Z"/></g>
<g><path style="opacity:1" fill="#86a6c0" d="M 115.5,19.5 C 115.5,21.1667 115.5,22.8333 115.5,24.5C 104.833,24.5 94.1667,24.5 83.5,24.5C 81.7041,23.2073 80.0375,21.7073 78.5,20C 90.8288,19.5001 103.162,19.3335 115.5,19.5 Z"/></g>
<g><path style="opacity:1" fill="#0b498d" d="M 136.5,20.5 C 136.5,21.5 136.5,22.5 136.5,23.5C 135.167,23.5 134.5,24.1667 134.5,25.5C 132.052,26.4731 129.385,26.8064 126.5,26.5C 128.29,25.2392 129.957,23.7392 131.5,22C 133.025,20.9914 134.692,20.4914 136.5,20.5 Z"/></g>
<g><path style="opacity:1" fill="#c0a092" d="M 140.5,19.5 C 140.249,21.1703 139.582,22.6703 138.5,24C 140.929,24.4512 141.096,25.2845 139,26.5C 137.436,26.0879 135.936,25.7546 134.5,25.5C 134.5,24.1667 135.167,23.5 136.5,23.5C 137.906,23.0269 138.573,22.0269 138.5,20.5C 138.842,19.6618 139.508,19.3284 140.5,19.5 Z"/></g>
<g><path style="opacity:1" fill="#dbb7a8" d="M 149.5,17.5 C 149.664,23.1667 149.997,23.5 150.5,18.5C 151.451,15.9548 152.451,15.9548 153.5,18.5C 151.914,20.1727 151.247,22.1727 151.5,24.5C 151.5,25.1667 151.5,25.8333 151.5,26.5C 150.25,26.4226 149.417,25.756 149,24.5C 146.099,27.3178 143.599,26.9845 141.5,23.5C 141.863,22.8165 142.196,22.1499 142.5,21.5C 143.376,21.6309 144.043,21.2975 144.5,20.5C 143.294,18.4841 143.294,16.4841 144.5,14.5C 146.029,15.7629 147.695,16.7629 149.5,17.5 Z"/></g>
<g><path style="opacity:1" fill="#e2bead" d="M 59.5,17.5 C 60.5,18.5 61.5,19.5 62.5,20.5C 62.8333,21.5 63.5,22.1667 64.5,22.5C 66.7742,23.9516 68.2742,23.285 69,20.5C 69.0976,22.6193 69.9309,24.286 71.5,25.5C 71.8072,24.1125 71.8072,22.4458 71.5,20.5C 72.8333,21.1667 73.8333,22.1667 74.5,23.5C 73.596,24.209 73.2627,25.209 73.5,26.5C 68.1667,26.5 62.8333,26.5 57.5,26.5C 57.6776,25.0637 58.0109,23.5637 58.5,22C 58.1081,20.9895 57.4415,20.4895 56.5,20.5C 56.8793,18.7869 57.8793,17.7869 59.5,17.5 Z"/></g>
<g><path style="opacity:1" fill="#114a8c" d="M 156.5,20.5 C 156.833,20.5 157.167,20.5 157.5,20.5C 158.684,21.6879 158.684,22.8545 157.5,24C 158.044,24.7172 158.711,25.2172 159.5,25.5C 159.5,25.8333 159.5,26.1667 159.5,26.5C 156.833,26.5 154.167,26.5 151.5,26.5C 151.5,25.8333 151.5,25.1667 151.5,24.5C 153.389,23.396 155.055,22.0627 156.5,20.5 Z"/></g>
<g><path style="opacity:1" fill="#cd967a" d="M 43.5,26.5 C 42.8333,26.8333 42.1667,27.1667 41.5,27.5C 38.5,27.8333 37.1667,26.5 37.5,23.5C 38.4728,22.2086 39.4728,22.2086 40.5,23.5C 41.3857,22.675 41.719,21.675 41.5,20.5C 42.1515,19.9094 42.8182,19.2427 43.5,18.5C 43.8333,19.1667 44.1667,19.8333 44.5,20.5C 43.8444,22.4534 43.5111,24.4534 43.5,26.5 Z"/></g>
<g><path style="opacity:1" fill="#ce544e" d="M 51.5,26.5 C 51.1667,26.5 50.8333,26.5 50.5,26.5C 49.8333,26.5 49.1667,26.5 48.5,26.5C 48.4176,24.7483 49.2509,23.7483 51,23.5C 52.6833,24.1765 52.8499,25.1765 51.5,26.5 Z"/></g>
<g><path style="opacity:1" fill="#41769b" d="M 115.5,19.5 C 116.468,21.6074 116.802,23.9407 116.5,26.5C 105.828,26.6665 95.1615,26.4998 84.5,26C 83.944,25.6174 83.6107,25.1174 83.5,24.5C 94.1667,24.5 104.833,24.5 115.5,24.5C 115.5,22.8333 115.5,21.1667 115.5,19.5 Z"/></g>
<g><path style="opacity:1" fill="#86a6c1" d="M 174.5,19.5 C 186.946,17.2274 197.446,20.5607 206,29.5C 208.354,33.2023 209.854,37.2023 210.5,41.5C 209.094,41.0269 208.427,40.0269 208.5,38.5C 204.778,31.6179 199.112,27.1179 191.5,25C 185.843,24.5006 180.176,24.3339 174.5,24.5C 174.5,22.8333 174.5,21.1667 174.5,19.5 Z"/></g>
<g><path style="opacity:1" fill="#998a87" d="M 37.5,20.5 C 37.5,21.5 37.5,22.5 37.5,23.5C 37.1667,26.5 38.5,27.8333 41.5,27.5C 40.1516,28.6006 38.6516,29.6006 37,30.5C 36.1224,27.7846 34.6224,25.4513 32.5,23.5C 34.0287,22.2371 35.6954,21.2371 37.5,20.5 Z"/></g>
<g><path style="opacity:1" fill="#665346" d="M 74.5,23.5 C 78.0933,25.1693 78.0933,26.336 74.5,27C 66.3225,27.8228 58.3225,27.6561 50.5,26.5C 50.8333,26.5 51.1667,26.5 51.5,26.5C 52.8333,26.5 54.1667,26.5 55.5,26.5C 56.1667,26.5 56.8333,26.5 57.5,26.5C 62.8333,26.5 68.1667,26.5 73.5,26.5C 73.2627,25.209 73.596,24.209 74.5,23.5 Z"/></g>
<g><path style="opacity:1" fill="#40779c" d="M 174.5,19.5 C 174.5,21.1667 174.5,22.8333 174.5,24.5C 180.176,24.3339 185.843,24.5006 191.5,25C 199.112,27.1179 204.778,31.6179 208.5,38.5C 207.508,45.9818 207.175,53.6484 207.5,61.5C 205.983,61.4894 204.816,60.8228 204,59.5C 208.171,35.6827 198.338,24.516 174.5,26C 173.189,23.7265 173.189,21.5598 174.5,19.5 Z"/></g>
<g><path style="opacity:1" fill="#ba927d" d="M 27.5,28.5 C 29.1207,28.7869 30.1207,29.7869 30.5,31.5C 29.1667,32.8333 27.8333,32.8333 26.5,31.5C 27.1272,30.5832 27.4606,29.5832 27.5,28.5 Z"/></g>
<g><path style="opacity:1" fill="#7da0b9" d="M 120.5,76.5 C 118.765,72.3797 115.598,70.7131 111,71.5C 105.819,71.0065 101.986,73.0065 99.5,77.5C 98.596,78.209 98.2627,79.209 98.5,80.5C 77.6664,84.9718 57.9997,81.9718 39.5,71.5C 37.8958,70.713 37.2291,69.3797 37.5,67.5C 38.3333,59.9164 38.3333,52.4164 37.5,45C 38.384,39.1156 41.7174,35.4489 47.5,34C 92.7684,33.715 138.268,33.8816 184,34.5C 184.689,33.6433 185.522,32.9767 186.5,32.5C 189.167,34.5 191.833,36.5 194.5,38.5C 195.407,41.0567 196.407,43.5567 197.5,46C 197.369,60.1958 197.369,73.1958 197.5,85C 197.37,86.0588 196.87,86.8921 196,87.5C 171.344,79.4158 146.178,75.7491 120.5,76.5 Z"/></g>
<g><path style="opacity:1" fill="#a73235" d="M 29.5,37.5 C 32.5572,36.0303 33.0572,36.697 31,39.5C 30.7216,38.5842 30.2216,37.9175 29.5,37.5 Z"/></g>
<g><path style="opacity:1" fill="#305781" d="M 208.5,38.5 C 208.427,40.0269 209.094,41.0269 210.5,41.5C 210.831,48.8635 210.497,56.1968 209.5,63.5C 208.478,63.1445 207.811,62.4778 207.5,61.5C 207.175,53.6484 207.508,45.9818 208.5,38.5 Z"/></g>
<g><path style="opacity:1" fill="#40769b" d="M 27.5,42.5 C 30.9788,46.6058 32.3121,51.4391 31.5,57C 31.3333,60.1667 31.1667,63.3333 31,66.5C 29.3308,67.8463 28.1642,69.513 27.5,71.5C 26.1667,61.8333 26.1667,52.1667 27.5,42.5 Z"/></g>
<g><path style="opacity:1" fill="#305784" d="M 27.5,42.5 C 26.1667,52.1667 26.1667,61.8333 27.5,71.5C 27.5,77.5 27.5,83.5 27.5,89.5C 26.1667,112.167 26.1667,134.833 27.5,157.5C 27.5,163.5 27.5,169.5 27.5,175.5C 26.3427,184.324 26.176,193.324 27,202.5C 28.117,208.069 30.7837,212.736 35,216.5C 35.3988,216.272 35.5654,215.938 35.5,215.5C 37.4333,217.596 39.7666,219.096 42.5,220C 92.1667,220.667 141.833,220.667 191.5,220C 194.862,218.657 197.862,216.824 200.5,214.5C 204.11,210.953 206.443,206.62 207.5,201.5C 207.145,197.202 206.978,192.869 207,188.5C 206.329,189.252 205.496,189.586 204.5,189.5C 206.127,187.37 207.96,185.37 210,183.5C 210.667,191.833 210.667,200.167 210,208.5C 207.234,217.598 201.401,223.765 192.5,227C 142.5,227.667 92.5,227.667 42.5,227C 32.9247,224.423 27.0913,218.257 25,208.5C 24.3333,151.833 24.3333,95.1667 25,38.5C 25.6622,39.9839 26.4955,41.3172 27.5,42.5 Z"/></g>
<g><path style="opacity:1" fill="#94292d" d="M 212.5,83.5 C 211.596,84.209 211.263,85.209 211.5,86.5C 210.175,87.9526 209.342,87.6192 209,85.5C 208.283,86.0442 207.783,86.7109 207.5,87.5C 206.417,87.4606 205.417,87.1272 204.5,86.5C 203.336,80.1928 203.169,73.8595 204,67.5C 208.623,70.4353 213.29,73.4353 218,76.5C 217.874,79.0977 216.874,81.431 215,83.5C 214.023,84.7736 213.189,84.7736 212.5,83.5 Z"/></g>
<g><path style="opacity:1" fill="#dfd3b6" d="M 31.5,68.5 C 32.8333,68.5 34.1667,68.5 35.5,68.5C 35.5,69.5 35.5,70.5 35.5,71.5C 34.1667,71.5 32.8333,71.5 31.5,71.5C 31.5,70.5 31.5,69.5 31.5,68.5 Z"/></g>
<g><path style="opacity:1" fill="#618dac" d="M 198.5,87.5 C 197.778,87.9175 197.278,88.5842 197,89.5C 196.5,124.832 196.333,160.165 196.5,195.5C 181.366,197.788 166.2,199.455 151,200.5C 131.83,200.006 112.996,195.506 94.5,187C 75.1516,177.242 56.6516,166.076 39,153.5C 38.5172,154.448 38.3505,155.448 38.5,156.5C 37.71,155.391 37.21,154.058 37,152.5C 36.3333,133.167 36.3333,113.833 37,94.5C 37.2896,92.9559 37.9563,91.6226 39,90.5C 39.4995,84.1754 39.6662,77.8421 39.5,71.5C 57.9997,81.9718 77.6664,84.9718 98.5,80.5C 98.2627,79.209 98.596,78.209 99.5,77.5C 100.195,82.5915 101.028,87.5915 102,92.5C 94.2296,97.9383 89.2296,105.272 87,114.5C 82.17,115.154 77.3367,115.987 72.5,117C 70.5359,118.451 69.2026,120.284 68.5,122.5C 63.2799,128.852 63.6133,135.018 69.5,141C 76.2685,141.144 82.9352,140.31 89.5,138.5C 92.3092,142.477 95.3092,146.31 98.5,150C 102.351,152.169 106.185,154.336 110,156.5C 109.672,161.475 109.672,166.475 110,171.5C 110.827,176.843 113.993,179.176 119.5,178.5C 128.105,178.898 131.772,174.898 130.5,166.5C 131.794,165.71 132.961,164.71 134,163.5C 134.747,160.076 134.58,156.743 133.5,153.5C 138.785,148.041 143.118,141.874 146.5,135C 152.756,135.579 159.089,135.579 165.5,135C 167,134.167 168.167,133 169,131.5C 169.667,128.167 169.667,124.833 169,121.5C 168.303,119.416 167.136,117.749 165.5,116.5C 165.161,101.243 158.328,97.5768 145,105.5C 139.269,97.0533 131.436,91.7199 121.5,89.5C 121.414,85.1434 121.08,80.81 120.5,76.5C 146.178,75.7491 171.344,79.4158 196,87.5C 196.87,86.8921 197.37,86.0588 197.5,85C 197.369,73.1958 197.369,60.1958 197.5,46C 196.407,43.5567 195.407,41.0567 194.5,38.5C 191.833,36.5 189.167,34.5 186.5,32.5C 185.522,32.9767 184.689,33.6433 184,34.5C 138.268,33.8816 92.7684,33.715 47.5,34C 41.7174,35.4489 38.384,39.1156 37.5,45C 38.3333,52.4164 38.3333,59.9164 37.5,67.5C 36.8826,67.3893 36.3826,67.056 36,66.5C 35.3333,57.8333 35.3333,49.1667 36,40.5C 37.4421,36.7251 39.9421,33.8917 43.5,32C 92.5,31.3333 141.5,31.3333 190.5,32C 194.219,33.263 196.719,35.763 198,39.5C 198.5,55.4965 198.667,71.4965 198.5,87.5 Z"/></g>
<g><path style="opacity:1" fill="#1c1e20" d="M 120.5,76.5 C 121.08,80.81 121.414,85.1434 121.5,89.5C 131.436,91.7199 139.269,97.0533 145,105.5C 158.328,97.5768 165.161,101.243 165.5,116.5C 165.16,118.679 164.16,120.512 162.5,122C 157.599,123.325 152.599,123.825 147.5,123.5C 145.775,133.783 140.441,141.616 131.5,147C 132.117,149.277 132.784,151.444 133.5,153.5C 134.58,156.743 134.747,160.076 134,163.5C 132.961,164.71 131.794,165.71 130.5,166.5C 116.367,170.54 110.534,165.206 113,150.5C 103.526,148.181 96.0263,143.015 90.5,135C 86.1454,134.964 81.9787,135.798 78,137.5C 72.5913,137.081 69.5913,134.081 69,128.5C 68.5045,126.527 68.3379,124.527 68.5,122.5C 69.2026,120.284 70.5359,118.451 72.5,117C 77.3367,115.987 82.17,115.154 87,114.5C 89.2296,105.272 94.2296,97.9383 102,92.5C 101.028,87.5915 100.195,82.5915 99.5,77.5C 101.986,73.0065 105.819,71.0065 111,71.5C 115.598,70.7131 118.765,72.3797 120.5,76.5 Z"/></g>
<g><path style="opacity:1" fill="#f5ecab" d="M 115.5,82.5 C 111.435,79.6349 108.101,80.3016 105.5,84.5C 104.637,82.1046 104.637,79.7713 105.5,77.5C 108.758,76.5827 112.091,76.2493 115.5,76.5C 115.5,78.5 115.5,80.5 115.5,82.5 Z"/></g>
<g><path style="opacity:1" fill="#4d7da0" d="M 29.5,73.5 C 32.1873,73.3359 34.854,73.5026 37.5,74C 31.0255,75.2148 31.0255,76.3814 37.5,77.5C 37.5,79.8333 37.5,82.1667 37.5,84.5C 31.627,84.5981 31.2936,85.5981 36.5,87.5C 34.3052,88.7691 32.1385,88.7691 30,87.5C 29.5008,82.8452 29.3342,78.1785 29.5,73.5 Z"/></g>
<g><path style="opacity:1" fill="#492f2e" d="M 212.5,83.5 C 212.338,81.4727 212.505,79.4727 213,77.5C 214.493,79.5632 214.326,81.5632 212.5,83.5 Z"/></g>
<g><path style="opacity:1" fill="#ead961" d="M 115.5,82.5 C 116.652,88.3265 117.152,94.3265 117,100.5C 115,101.167 113,101.833 111,102.5C 108.864,96.631 107.031,90.631 105.5,84.5C 108.101,80.3016 111.435,79.6349 115.5,82.5 Z"/></g>
<g><path style="opacity:1" fill="#563231" d="M 211.5,86.5 C 209.657,90.6897 206.99,94.3564 203.5,97.5C 202.13,94.5153 202.63,91.8486 205,89.5C 205.333,89.8333 205.667,90.1667 206,90.5C 206.5,89.3333 207.333,88.5 208.5,88C 208.272,87.6012 207.938,87.4346 207.5,87.5C 207.783,86.7109 208.283,86.0442 209,85.5C 209.342,87.6192 210.175,87.9526 211.5,86.5 Z"/></g>
<g><path style="opacity:1" fill="#e8d85f" d="M 31.5,90.5 C 37.1857,90.0048 37.6857,91.0048 33,93.5C 31.9614,92.7562 31.4614,91.7562 31.5,90.5 Z"/></g>
<g><path style="opacity:1" fill="#3e7398" d="M 27.5,89.5 C 28.9073,91.3083 30.074,93.3083 31,95.5C 31.6667,114.5 31.6667,133.5 31,152.5C 29.8236,154.196 28.657,155.862 27.5,157.5C 26.1667,134.833 26.1667,112.167 27.5,89.5 Z"/></g>
<g><path style="opacity:1" fill="#f1e7a9" d="M 122.5,94.5 C 130.281,96.0291 135.614,100.362 138.5,107.5C 136.599,107.433 134.766,107.767 133,108.5C 130.511,106.681 128.011,104.848 125.5,103C 124.552,102.517 123.552,102.351 122.5,102.5C 122.5,99.8333 122.5,97.1667 122.5,94.5 Z"/></g>
<g><path style="opacity:1" fill="#ede1a6" d="M 102.5,97.5 C 103.75,97.5774 104.583,98.244 105,99.5C 105.681,101.529 106.014,103.529 106,105.5C 103.295,107.707 101.129,110.374 99.5,113.5C 97.2573,114.474 94.924,114.808 92.5,114.5C 92.4598,111.575 93.2932,108.909 95,106.5C 97.3347,103.335 99.8347,100.335 102.5,97.5 Z"/></g>
<g><path style="opacity:1" fill="#2f5681" d="M 206.5,130.5 C 207.68,120.655 207.68,110.989 206.5,101.5C 207.458,100.038 208.624,98.7051 210,97.5C 210.833,109.514 210.666,121.514 209.5,133.5C 208.145,132.812 207.145,131.812 206.5,130.5 Z"/></g>
<g><path style="opacity:1" fill="#85a5c2" d="M 128.5,108.5 C 128.565,108.938 128.399,109.272 128,109.5C 121.383,104.61 114.55,104.277 107.5,108.5C 106.893,108.376 106.56,108.043 106.5,107.5C 114.195,101.987 121.528,102.32 128.5,108.5 Z"/></g>
<g><path style="opacity:1" fill="#40789e" d="M 206.5,101.5 C 207.68,110.989 207.68,120.655 206.5,130.5C 205.542,130.047 204.708,129.381 204,128.5C 203.333,120.5 203.333,112.5 204,104.5C 204.377,103.058 205.21,102.058 206.5,101.5 Z"/></g>
<g><path style="opacity:1" fill="#efe4a8" d="M 160.5,110.5 C 155.809,110.77 151.309,111.77 147,113.5C 146.667,113.167 146.333,112.833 146,112.5C 142.391,114.464 138.891,115.798 135.5,116.5C 134.708,115.033 134.708,113.7 135.5,112.5C 142.666,110.027 149.999,108.027 157.5,106.5C 158.984,107.467 159.984,108.8 160.5,110.5 Z"/></g>
<g><path style="opacity:1" fill="#ead961" d="M 160.5,110.5 C 160.958,113.394 160.291,115.894 158.5,118C 151.13,118.725 143.797,119.558 136.5,120.5C 135.571,119.311 135.238,117.978 135.5,116.5C 138.891,115.798 142.391,114.464 146,112.5C 146.333,112.833 146.667,113.167 147,113.5C 151.309,111.77 155.809,110.77 160.5,110.5 Z"/></g>
<g><path style="opacity:1" fill="#1b1b1d" d="M 129.5,118.5 C 128.275,129.294 122.275,133.46 111.5,131C 106.634,127.769 104.634,123.269 105.5,117.5C 108.19,107.99 114.19,105.157 123.5,109C 126.98,111.297 128.98,114.464 129.5,118.5 Z"/></g>
<g><path style="opacity:1" fill="#262322" d="M 118.5,112.5 C 125.97,116.528 126.303,121.028 119.5,126C 117.115,126.556 114.782,126.39 112.5,125.5C 108.413,118.341 110.413,114.008 118.5,112.5 Z"/></g>
<g><path style="opacity:1" fill="#efe062" d="M 115.5,110.5 C 117.179,110.285 118.179,110.952 118.5,112.5C 110.413,114.008 108.413,118.341 112.5,125.5C 111.833,125.833 111.167,126.167 110.5,126.5C 106.517,121.779 106.517,116.946 110.5,112C 112.025,110.991 113.692,110.491 115.5,110.5 Z"/></g>
<g><path style="opacity:1" fill="#44789f" d="M 128.5,108.5 C 130.316,110.118 131.649,112.118 132.5,114.5C 132.665,117.518 132.498,120.518 132,123.5C 131.626,121.509 130.793,119.842 129.5,118.5C 128.98,114.464 126.98,111.297 123.5,109C 114.19,105.157 108.19,107.99 105.5,117.5C 104.954,120.647 103.954,123.647 102.5,126.5C 100.145,119.209 101.478,112.875 106.5,107.5C 106.56,108.043 106.893,108.376 107.5,108.5C 114.55,104.277 121.383,104.61 128,109.5C 128.399,109.272 128.565,108.938 128.5,108.5 Z"/></g>
<g><path style="opacity:1" fill="#ebda60" d="M 121.5,120.5 C 118.609,119.597 115.609,119.264 112.5,119.5C 114.378,115.09 117.211,114.423 121,117.5C 121.483,118.448 121.649,119.448 121.5,120.5 Z"/></g>
<g><path style="opacity:1" fill="#46608f" d="M 165.5,116.5 C 167.136,117.749 168.303,119.416 169,121.5C 169.667,124.833 169.667,128.167 169,131.5C 168.167,133 167,134.167 165.5,135C 159.089,135.579 152.756,135.579 146.5,135C 143.118,141.874 138.785,148.041 133.5,153.5C 132.784,151.444 132.117,149.277 131.5,147C 140.441,141.616 145.775,133.783 147.5,123.5C 152.599,123.825 157.599,123.325 162.5,122C 164.16,120.512 165.16,118.679 165.5,116.5 Z"/></g>
<g><path style="opacity:1" fill="#f4ebac" d="M 98.5,123.5 C 92.6957,123.467 86.8623,123.467 81,123.5C 78.544,125.285 76.044,125.952 73.5,125.5C 73.4208,124.07 73.7542,122.736 74.5,121.5C 82.4818,120.56 90.4818,119.893 98.5,119.5C 98.5,120.833 98.5,122.167 98.5,123.5 Z"/></g>
<g><path style="opacity:1" fill="#ead860" d="M 98.5,123.5 C 99.8284,124.91 99.4951,126.076 97.5,127C 90.8333,128.833 84.1667,130.667 77.5,132.5C 74.7013,131.189 73.368,128.855 73.5,125.5C 76.044,125.952 78.544,125.285 81,123.5C 86.8623,123.467 92.6957,123.467 98.5,123.5 Z"/></g>
<g><path style="opacity:1" fill="#46618f" d="M 68.5,122.5 C 68.3379,124.527 68.5045,126.527 69,128.5C 69.5913,134.081 72.5913,137.081 78,137.5C 81.9787,135.798 86.1454,134.964 90.5,135C 96.0263,143.015 103.526,148.181 113,150.5C 110.534,165.206 116.367,170.54 130.5,166.5C 131.772,174.898 128.105,178.898 119.5,178.5C 113.993,179.176 110.827,176.843 110,171.5C 109.672,166.475 109.672,161.475 110,156.5C 106.185,154.336 102.351,152.169 98.5,150C 95.3092,146.31 92.3092,142.477 89.5,138.5C 82.9352,140.31 76.2685,141.144 69.5,141C 63.6133,135.018 63.2799,128.852 68.5,122.5 Z"/></g>
<g><path style="opacity:1" fill="#2f557f" d="M 132.5,114.5 C 134.132,129.7 127.132,136.533 111.5,135C 107.342,133.341 104.342,130.508 102.5,126.5C 103.954,123.647 104.954,120.647 105.5,117.5C 104.634,123.269 106.634,127.769 111.5,131C 122.275,133.46 128.275,129.294 129.5,118.5C 130.793,119.842 131.626,121.509 132,123.5C 132.498,120.518 132.665,117.518 132.5,114.5 Z"/></g>
<g><path style="opacity:1" fill="#c1a94b" d="M 112.5,119.5 C 115.609,119.264 118.609,119.597 121.5,120.5C 117.972,124.727 114.972,124.394 112.5,119.5 Z"/></g>
<g><path style="opacity:1" fill="#ccb24f" d="M 115.5,110.5 C 124.386,109.917 127.886,114.25 126,123.5C 121.867,130.16 116.7,131.16 110.5,126.5C 111.167,126.167 111.833,125.833 112.5,125.5C 114.782,126.39 117.115,126.556 119.5,126C 126.303,121.028 125.97,116.528 118.5,112.5C 118.179,110.952 117.179,110.285 115.5,110.5 Z"/></g>
<g><path style="opacity:1" fill="#ecda61" d="M 134.5,124.5 C 137.187,124.336 139.854,124.503 142.5,125C 140.578,132.009 136.578,137.509 130.5,141.5C 129.448,139.458 128.781,137.292 128.5,135C 130.741,131.603 132.741,128.103 134.5,124.5 Z"/></g>
<g><path style="opacity:1" fill="#ecdb61" d="M 100.5,130.5 C 104.028,133.188 107.861,135.521 112,137.5C 112.497,139.81 112.663,142.143 112.5,144.5C 105.133,143.152 99.4662,139.319 95.5,133C 97.3146,132.243 98.9812,131.41 100.5,130.5 Z"/></g>
<g><path style="opacity:1" fill="#ecda61" d="M 128.5,156.5 C 126.574,157.242 124.741,158.242 123,159.5C 121.67,158.418 120.17,157.751 118.5,157.5C 117.899,151.158 117.232,144.824 116.5,138.5C 119.127,137.908 121.794,137.575 124.5,137.5C 125.994,143.809 127.328,150.142 128.5,156.5 Z"/></g>
<g><path style="opacity:1" fill="#90a0b7" d="M 212.5,142.5 C 213.752,145.9 215.919,149.233 219,152.5C 220.961,158.412 219.127,162.412 213.5,164.5C 215.399,162.749 216.733,160.583 217.5,158C 216.701,155.851 215.867,153.684 215,151.5C 214.346,150.304 213.846,150.304 213.5,151.5C 214.731,154.538 214.564,157.538 213,160.5C 211.68,160.201 210.18,160.034 208.5,160C 207.944,159.617 207.611,159.117 207.5,158.5C 208.496,158.414 209.329,158.748 210,159.5C 210.464,158.906 210.631,158.239 210.5,157.5C 211.117,157.611 211.617,157.944 212,158.5C 213.187,156.956 213.52,155.289 213,153.5C 211.104,151.47 208.937,149.803 206.5,148.5C 205.482,149.193 204.482,149.859 203.5,150.5C 203.663,144.987 203.83,139.654 204,134.5C 207.003,137.067 209.836,139.734 212.5,142.5 Z"/></g>
<g><path style="opacity:1" fill="#e4c1b3" d="M 210.5,157.5 C 210.631,158.239 210.464,158.906 210,159.5C 209.329,158.748 208.496,158.414 207.5,158.5C 207.103,157.475 206.437,157.308 205.5,158C 206.337,159.011 206.67,160.178 206.5,161.5C 205.5,161.5 204.5,161.5 203.5,161.5C 203.5,157.833 203.5,154.167 203.5,150.5C 204.482,149.859 205.482,149.193 206.5,148.5C 208.937,149.803 211.104,151.47 213,153.5C 213.52,155.289 213.187,156.956 212,158.5C 211.617,157.944 211.117,157.611 210.5,157.5 Z"/></g>
<g><path style="opacity:1" fill="#4d565b" d="M 210.5,157.5 C 208.824,156.817 208.157,155.483 208.5,153.5C 209.5,153.5 210.5,153.5 211.5,153.5C 211.567,154.959 211.234,156.292 210.5,157.5 Z"/></g>
<g><path style="opacity:1" fill="#7da1b9" d="M 196.5,195.5 C 197.562,201.737 195.895,207.237 191.5,212C 190.5,212.333 189.5,212.667 188.5,213C 140.833,213.667 93.1667,213.667 45.5,213C 42.6667,211.5 40.5,209.333 39,206.5C 38.5002,196.839 38.3335,187.172 38.5,177.5C 39.7997,170.47 39.7997,163.47 38.5,156.5C 38.3505,155.448 38.5172,154.448 39,153.5C 56.6516,166.076 75.1516,177.242 94.5,187C 112.996,195.506 131.83,200.006 151,200.5C 166.2,199.455 181.366,197.788 196.5,195.5 Z"/></g>
<g><path style="opacity:1" fill="#d3c391" d="M 31.5,154.5 C 32.8333,154.5 34.1667,154.5 35.5,154.5C 35.5,155.5 35.5,156.5 35.5,157.5C 33.0274,157.912 31.6941,156.912 31.5,154.5 Z"/></g>
<g><path style="opacity:1" fill="#bea64b" d="M 128.5,156.5 C 129.249,157.531 129.915,158.698 130.5,160C 126.979,163.681 123.146,164.181 119,161.5C 118.51,160.207 118.343,158.873 118.5,157.5C 120.17,157.751 121.67,158.418 123,159.5C 124.741,158.242 126.574,157.242 128.5,156.5 Z"/></g>
<g><path style="opacity:1" fill="#aed1ee" d="M 213.5,164.5 C 213.918,165.222 214.584,165.722 215.5,166C 214.642,168.342 212.642,170.175 209.5,171.5C 207.833,170.667 206.167,169.833 204.5,169C 206.874,166.935 209.04,167.101 211,169.5C 210.981,167.893 210.815,166.226 210.5,164.5C 207.921,164.606 205.588,163.939 203.5,162.5C 203.5,162.167 203.5,161.833 203.5,161.5C 204.5,161.5 205.5,161.5 206.5,161.5C 206.67,160.178 206.337,159.011 205.5,158C 206.437,157.308 207.103,157.475 207.5,158.5C 207.611,159.117 207.944,159.617 208.5,160C 210.18,160.034 211.68,160.201 213,160.5C 214.564,157.538 214.731,154.538 213.5,151.5C 213.846,150.304 214.346,150.304 215,151.5C 215.867,153.684 216.701,155.851 217.5,158C 216.733,160.583 215.399,162.749 213.5,164.5 Z"/></g>
<g><path style="opacity:1" fill="#4a799d" d="M 29.5,159.5 C 32.1873,159.336 34.854,159.503 37.5,160C 32.203,161.134 32.203,161.968 37.5,162.5C 37.5,165.167 37.5,167.833 37.5,170.5C 31.7389,170.709 31.4055,171.542 36.5,173C 34.1902,173.497 31.8568,173.663 29.5,173.5C 29.5,168.833 29.5,164.167 29.5,159.5 Z"/></g>
<g><path style="opacity:1" fill="#e9ded9" d="M 212.5,142.5 C 217.648,145.913 222.648,149.58 227.5,153.5C 220.235,163.601 212.402,173.268 204,182.5C 203.5,175.842 203.334,169.175 203.5,162.5C 205.588,163.939 207.921,164.606 210.5,164.5C 210.815,166.226 210.981,167.893 211,169.5C 209.04,167.101 206.874,166.935 204.5,169C 206.167,169.833 207.833,170.667 209.5,171.5C 212.642,170.175 214.642,168.342 215.5,166C 214.584,165.722 213.918,165.222 213.5,164.5C 219.127,162.412 220.961,158.412 219,152.5C 215.919,149.233 213.752,145.9 212.5,142.5 Z"/></g>
<g><path style="opacity:1" fill="#f8eb67" d="M 31.5,176.5 C 32.9778,176.238 34.3112,176.571 35.5,177.5C 34.8333,177.833 34.1667,178.167 33.5,178.5C 32.2867,178.253 31.62,177.586 31.5,176.5 Z"/></g>
<g><path style="opacity:1" fill="#40759b" d="M 27.5,175.5 C 28.9073,177.308 30.074,179.308 31,181.5C 31.3333,190.833 31.6667,200.167 32,209.5C 32.926,211.692 34.0927,213.692 35.5,215.5C 35.5654,215.938 35.3988,216.272 35,216.5C 30.7837,212.736 28.117,208.069 27,202.5C 26.176,193.324 26.3427,184.324 27.5,175.5 Z"/></g>
<g><path style="opacity:1" fill="#41789f" d="M 200.5,214.5 C 201.045,212.407 201.878,210.407 203,208.5C 203.172,201.987 203.672,195.653 204.5,189.5C 205.496,189.586 206.329,189.252 207,188.5C 206.978,192.869 207.145,197.202 207.5,201.5C 206.443,206.62 204.11,210.953 200.5,214.5 Z"/></g>
<g><path style="opacity:1" fill="#477fa7" d="M 198.5,87.5 C 198.667,127.501 198.5,167.501 198,207.5C 196.833,211.333 194.333,213.833 190.5,215C 141.167,215.667 91.8333,215.667 42.5,215C 40.2911,213.127 38.4578,210.96 37,208.5C 36.3333,198.833 36.3333,189.167 37,179.5C 37.2784,178.584 37.7784,177.918 38.5,177.5C 38.3335,187.172 38.5002,196.839 39,206.5C 40.5,209.333 42.6667,211.5 45.5,213C 93.1667,213.667 140.833,213.667 188.5,213C 189.5,212.667 190.5,212.333 191.5,212C 195.895,207.237 197.562,201.737 196.5,195.5C 196.333,160.165 196.5,124.832 197,89.5C 197.278,88.5842 197.778,87.9175 198.5,87.5 Z"/></g>
<g><path style="opacity:1" fill="#42779d" d="M 162.5,233.5 C 168.833,233.5 175.167,233.5 181.5,233.5C 181.5,235.5 181.5,237.5 181.5,239.5C 175.794,239.829 170.128,239.495 164.5,238.5C 163.544,236.925 162.877,235.258 162.5,233.5 Z"/></g>
<g><path style="opacity:0.02" fill="#000000" d="M 153.5,236.5 C 154.833,237.167 154.833,237.167 153.5,236.5 Z"/></g>
<g><path style="opacity:1" fill="#4279a0" d="M 53.5,234.5 C 59.5368,234.171 65.5368,234.504 71.5,235.5C 70.9598,236.748 70.2931,237.915 69.5,239C 64.1771,239.499 58.8437,239.666 53.5,239.5C 53.5,237.833 53.5,236.167 53.5,234.5 Z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 32 KiB

39
public/svgs/glpi.svg Normal file
View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="433" height="232" viewBox="0 0 433 232" fill="none">
<g clip-path="url(#clip0_56_97)">
<path d="M214.842 126.922C206.399 126.922 197.955 126.678 189.511 127.013C184.26 127.226 182.357 125.826 183.186 120.193C188.313 85.239 193.134 50.2546 198.077 15.3006C198.2 14.4785 198.385 13.6564 198.507 12.8343C200.227 0.0158479 200.135 0.716145 213.123 0.716145C217.974 0.716145 219.479 2.32987 218.773 6.92748C215.978 25.7442 213.338 44.5609 210.636 63.408C208.64 77.4748 206.798 91.5721 204.618 105.609C204.065 109.11 205.047 109.963 208.364 109.902C219.97 109.749 231.577 110.023 243.183 109.78C248.127 109.658 250 111.059 249.416 116.235C248.219 126.557 248.065 126.922 237.595 126.922C230.011 126.922 222.396 126.922 214.812 126.922H214.842Z" fill="#00A5F3"></path>
<path d="M374.693 126.922C365.236 126.922 365.236 126.739 366.587 117.422C371.929 80.6416 377.18 43.8303 382.246 6.98857C382.891 2.29962 384.611 0.107387 389.37 0.503208C390.199 0.564103 391.059 0.503208 391.888 0.503208C402.542 0.503208 402.297 0.442312 400.761 11.0077C395.634 46.5706 390.721 82.1335 385.593 117.696C384.273 126.953 384.058 126.922 374.662 126.922H374.693Z" fill="#00A5F3"></path>
<path d="M320.315 0.716239H290.285C285.158 0.716239 282.885 2.20818 282.148 7.65832C277.236 44.3478 271.954 80.7024 266.735 117.331C265.353 126.922 265.261 126.922 274.656 126.922C284.728 126.922 284.728 126.922 285.987 117.148C286.816 110.724 287.89 104.33 288.627 97.8749C290.469 81.4636 290.408 81.4636 306.712 81.4636C307.572 81.4636 308.401 81.4636 309.261 81.4636C333.978 81.4636 352.279 65.6003 355.533 41.3639C358.113 22.2732 347.396 0.685791 320.315 0.685791V0.716239ZM310.029 63.3776C308.585 63.3776 307.112 63.3776 305.607 63.3776C293.755 63.1949 293.663 63.3776 295.075 51.7771C296.242 42.2469 297.808 32.7472 298.944 23.2171C299.343 19.807 300.756 18.589 304.195 18.7717C309.015 19.0153 313.897 18.7717 318.749 18.8326C328.359 18.924 334.378 24.3132 335.851 33.752C337.878 46.6923 328.943 63.134 310.029 63.3472V63.3776Z" fill="#00A5F3"></path>
<path d="M160.556 57.7449C153.033 44.4697 126.872 45.6267 102.155 60.3329C100.773 61.155 99.4527 62.038 98.1631 62.921C98.1631 62.921 98.071 63.0428 98.1017 63.0732C98.1017 63.1341 98.1938 63.1341 98.2552 63.1037C113.577 58.7801 126.105 58.9628 131.754 64.7174C134.856 67.884 135.5 72.4207 134.088 77.7795C133.105 82.9556 130.004 88.6493 125.245 94.1908C117.814 103.843 106.361 114.347 92.0835 123.908C78.9418 132.707 57.7247 142.664 45.5348 146.804C45.5348 146.804 45.3506 146.865 45.2585 146.926C44.7365 147.261 44.583 147.961 44.9207 148.449C45.1971 148.844 45.8112 148.966 46.2717 148.844C59.8126 145.556 71.9411 139.771 84.6222 134.169C98.5008 128.018 111.857 120.863 124.447 112.368C135.685 104.787 147.291 96.5962 155.151 85.4218C160.494 77.8404 165.622 66.6965 160.556 57.7144V57.7449Z" fill="#80D2F8"></path>
<path d="M106.793 18.589C120.579 13.626 134.919 12.2559 149.411 12.3168C153.004 12.3168 156.074 12.8648 156.32 7.78007C156.566 3.06067 154.877 1.17291 150.025 0.837989C135.932 -0.10589 121.93 0.898884 108.052 2.90843C88.8611 5.67917 70.2539 10.4899 54.2259 22.0905C23.6438 44.2564 24.6878 89.2581 56.2525 110.054C65.8017 116.326 76.3642 120.163 87.4794 122.659C93.9581 124.303 107.468 125.917 109.341 126.161C122.36 127.257 135.348 127.866 148.459 126.709C153.833 126.222 153.679 123.573 153.771 119.797C153.894 115.565 152.451 113.829 147.968 113.982C144.191 114.103 140.415 114.134 136.669 113.951C136.669 113.951 136.515 113.951 136.454 113.951C133.26 113.799 130.067 113.403 126.905 112.886C114.5 111.12 106.209 108.501 106.209 108.501C82.0446 100.493 72.0041 86.6396 71.8813 64.3215C71.7585 42.3382 83.5492 26.9926 106.731 18.6195L106.793 18.589Z" fill="#00A5F3"></path>
</g>
<path d="M228.628 177.309C227.459 177.28 226.435 176.898 225.441 176.34L218.63 172.931C216.876 172.049 215.443 172.931 215.443 174.9V182.011C215.443 183.98 216.847 186.331 218.601 187.243L225.471 190.828C225.617 190.916 225.792 190.975 225.938 191.034C226.786 191.416 227.663 191.651 228.628 191.651V177.309Z" fill="#AFD136"></path>
<path d="M228.63 196.352C227.46 196.323 226.437 195.941 225.443 195.383L209.394 186.86C207.639 185.978 206.207 186.86 206.207 188.829V196.852C206.207 198.821 207.61 201.172 209.364 202.083L225.502 210.782C225.648 210.87 225.823 210.929 225.969 210.988C226.817 211.37 227.694 211.605 228.659 211.605V196.352H228.63Z" fill="#923392"></path>
<path d="M228.629 216.19C227.46 216.161 226.437 215.778 225.443 215.22L199.687 200.849C197.933 199.968 196.501 200.849 196.501 202.818V210.841C196.501 212.81 197.904 215.161 199.658 216.072L225.472 230.619C225.618 230.708 225.794 230.766 225.94 230.825C226.788 231.207 227.665 231.442 228.629 231.442V216.19Z" fill="#923392"></path>
<path d="M238.892 173.018L231.817 176.545C230.648 177.103 229.624 177.338 228.63 177.309V191.65C229.595 191.65 230.618 191.415 231.758 190.828L238.892 187.095C240.616 186.184 242.049 183.833 242.049 181.864V174.987C242.049 173.018 240.616 172.137 238.862 173.018" fill="#D2DD26"></path>
<path d="M238.892 192.062L231.817 195.588C230.648 196.147 229.624 196.382 228.63 196.352V211.605C229.595 211.605 230.618 211.37 231.758 210.782L238.892 207.02C240.616 206.109 242.049 203.758 242.049 201.789V194.031C242.049 192.062 240.616 191.18 238.862 192.062" fill="#A45CA4"></path>
<path d="M238.892 211.899L231.817 215.425C230.648 215.984 229.624 216.219 228.63 216.189V231.442C229.595 231.442 230.618 231.207 231.758 230.619L238.892 226.857C240.616 225.946 242.049 223.595 242.049 221.626V213.868C242.049 211.899 240.616 211.017 238.862 211.899" fill="#A45CA4"></path>
<path d="M305.603 215.66L312.122 216.483C311.099 220.303 309.199 223.301 306.393 225.417C303.615 227.533 300.049 228.591 295.722 228.591C290.255 228.591 285.958 226.916 282.742 223.536C279.556 220.157 277.948 215.425 277.948 209.342C277.948 203.258 279.556 198.145 282.801 194.677C286.016 191.209 290.226 189.446 295.371 189.446C300.517 189.446 304.434 191.15 307.591 194.56C310.748 197.969 312.327 202.759 312.327 208.96C312.327 209.342 312.327 209.9 312.298 210.664H284.496C284.73 214.779 285.899 217.923 287.975 220.127C290.051 222.302 292.653 223.419 295.781 223.419C298.09 223.419 300.078 222.801 301.715 221.597C303.352 220.362 304.639 218.423 305.603 215.748M284.876 205.404H305.691C305.399 202.259 304.609 199.879 303.294 198.321C301.277 195.882 298.675 194.648 295.459 194.648C292.565 194.648 290.109 195.617 288.15 197.586C286.163 199.526 285.081 202.142 284.876 205.433" fill="#5B5A5D"></path>
<path d="M344.162 214.015L350.389 214.837C349.717 219.128 347.962 222.508 345.185 224.947C342.379 227.386 338.958 228.591 334.895 228.591C329.808 228.591 325.686 226.916 322.587 223.566C319.488 220.216 317.939 215.396 317.939 209.166C317.939 205.11 318.611 201.583 319.927 198.556C321.272 195.53 323.289 193.237 326.008 191.738C328.726 190.21 331.708 189.476 334.895 189.476C338.929 189.476 342.262 190.504 344.834 192.561C347.407 194.618 349.044 197.528 349.775 201.319L343.636 202.259C343.051 199.732 342.028 197.851 340.537 196.587C339.046 195.324 337.263 194.677 335.158 194.677C331.971 194.677 329.399 195.823 327.411 198.116C325.423 200.408 324.429 204.052 324.429 208.989C324.429 213.926 325.394 217.688 327.294 219.951C329.223 222.243 331.708 223.36 334.807 223.36C337.292 223.36 339.368 222.596 341.005 221.068C342.671 219.54 343.723 217.189 344.162 214.015Z" fill="#5B5A5D"></path>
<path d="M372.929 176.016H379.244V183.334H372.929V176.016ZM372.929 190.27H379.244V227.739H372.929V190.27Z" fill="#5B5A5D"></path>
<path d="M425.814 182.863V175.487H432.596V181.305C432.596 184.45 432.245 186.713 431.514 188.123C430.52 190.034 428.942 191.474 426.808 192.414L425.258 189.916C426.515 189.387 427.451 188.594 428.094 187.477C428.708 186.36 429.059 184.832 429.117 182.863H425.814Z" fill="#A45CA4"></path>
<path d="M356.237 179.307V227.739H362.552V175.986L356.237 179.307Z" fill="#5B5A5D"></path>
<path d="M420.61 200.82C419.85 198.439 418.827 196.412 417.511 194.737C416.195 193.061 414.5 191.739 412.454 190.828C410.407 189.887 408.244 189.447 405.934 189.447C401.783 189.447 398.392 191.122 395.732 194.501V176.016L389.417 179.308V227.739H395.264V223.037C397.778 226.74 401.228 228.562 405.613 228.562C409.998 228.562 413.828 226.799 416.985 223.302C420.142 219.805 421.721 214.838 421.721 208.402C421.721 205.698 421.341 203.171 420.61 200.79M412.249 219.746C410.232 222.156 407.864 223.36 405.087 223.36C401.695 223.36 399.006 221.715 397.018 218.453C395.79 216.454 395.205 213.222 395.205 208.725C395.205 204.229 396.199 200.673 398.217 198.263C400.234 195.853 402.602 194.648 405.379 194.648C408.156 194.648 410.436 195.824 412.395 198.146C414.324 200.467 415.289 204.082 415.289 208.99C415.289 213.898 414.295 217.365 412.278 219.775" fill="#5B5B5A"></path>
<path d="M274.119 222.066C272.979 222.243 272.043 222.302 271.342 222.302C270.406 222.302 269.675 222.155 269.178 221.831C268.681 221.537 268.301 221.126 268.097 220.597C267.863 220.068 267.775 218.922 267.775 217.1V195.176H274.119V190.239H267.775V177.161L261.49 180.453V190.269H256.871V195.206H261.49V216.777C261.49 220.597 261.753 223.095 262.25 224.271C262.776 225.446 263.653 226.416 264.91 227.151C266.167 227.885 267.95 228.238 270.231 228.238C271.634 228.238 273.213 228.062 274.996 227.68L274.09 222.066H274.119Z" fill="#5B5A5D"></path>
<path d="M11.6542 199.262C13.0866 199.997 14.1683 201.025 14.9576 202.406C15.7177 203.758 16.127 205.345 16.127 207.167C16.127 208.989 15.7469 210.547 14.9576 211.899C14.1683 213.221 13.0866 214.25 11.6542 214.955C10.2217 215.66 8.58458 216.013 6.68436 216.013H0.895996V198.174H6.68436C8.58458 198.174 10.2217 198.527 11.6542 199.262ZM11.4495 211.928C12.5604 210.811 13.1159 209.224 13.1159 207.167C13.1159 205.11 12.5604 203.494 11.4495 202.318C10.3386 201.143 8.75998 200.584 6.68436 200.584H3.79018V213.633H6.68436C8.75998 213.633 10.3386 213.074 11.4495 211.928Z" fill="#B3B3B3"></path>
<path d="M21.9162 200.525V205.756H28.0553V208.137H21.9162V213.603H28.8154V215.984H19.022V198.116H28.8154V200.496H21.9162V200.525Z" fill="#B3B3B3"></path>
<path d="M47.6707 198.174L41.0053 216.013H37.6434L30.9487 198.174H34.0768L39.3389 213.015L44.6303 198.174H47.6999H47.6707Z" fill="#B3B3B3"></path>
<path d="M52.9904 200.525V205.756H59.1295V208.137H52.9904V213.603H59.8896V215.984H50.0962V198.116H59.8896V200.496H52.9904V200.525Z" fill="#B3B3B3"></path>
<path d="M66.379 213.633H72.372V215.984H63.4556V198.145H66.379V213.603V213.633Z" fill="#B3B3B3"></path>
<path d="M78.3374 215.013C76.9634 214.249 75.8525 213.162 75.034 211.751C74.2154 210.37 73.8062 208.783 73.8062 207.049C73.8062 205.315 74.2154 203.728 75.034 202.347C75.8525 200.966 76.9342 199.879 78.3374 199.114C79.7115 198.35 81.2316 197.939 82.898 197.939C84.5643 197.939 86.0845 198.321 87.4877 199.114C88.8617 199.908 89.9726 200.966 90.762 202.347C91.5513 203.728 91.9606 205.286 91.9606 207.049C91.9606 208.812 91.5513 210.37 90.762 211.751C89.9726 213.133 88.8617 214.22 87.4877 215.013C86.1137 215.807 84.5643 216.189 82.898 216.189C81.2316 216.189 79.7407 215.807 78.3374 215.013ZM86.026 212.839C86.9323 212.31 87.6631 211.516 88.1894 210.517C88.7156 209.518 88.9787 208.372 88.9787 207.049C88.9787 205.727 88.7156 204.581 88.1894 203.581C87.6631 202.582 86.9615 201.818 86.026 201.289C85.0905 200.76 84.0673 200.496 82.898 200.496C81.7286 200.496 80.6762 200.76 79.7699 201.289C78.8637 201.818 78.1328 202.582 77.6066 203.581C77.0804 204.581 76.8173 205.727 76.8173 207.049C76.8173 208.372 77.0804 209.518 77.6066 210.517C78.1328 211.516 78.8344 212.28 79.7699 212.839C80.7054 213.397 81.7286 213.632 82.898 213.632C84.0673 213.632 85.1198 213.368 86.026 212.839Z" fill="#B3B3B3"></path>
<path d="M106.577 206.051C106.139 206.844 105.466 207.52 104.531 208.02C103.595 208.519 102.397 208.784 100.935 208.784H97.7193V216.013H94.7959V198.174H100.906C102.28 198.174 103.42 198.41 104.355 198.88C105.291 199.35 105.993 199.997 106.46 200.79C106.928 201.584 107.162 202.495 107.162 203.494C107.162 204.405 106.957 205.257 106.519 206.051H106.577ZM103.42 205.61C103.975 205.11 104.239 204.405 104.239 203.464C104.239 201.525 103.128 200.526 100.935 200.526H97.7193V206.344H100.935C102.046 206.344 102.865 206.08 103.42 205.58V205.61Z" fill="#B3B3B3"></path>
<path d="M112.89 200.525V205.756H119.029V208.137H112.89V213.603H119.79V215.984H109.996V198.116H119.79V200.496H112.89V200.525Z" fill="#B3B3B3"></path>
<path d="M134.116 199.262C135.548 199.997 136.63 201.025 137.419 202.406C138.179 203.758 138.588 205.345 138.588 207.167C138.588 208.989 138.208 210.547 137.419 211.899C136.63 213.221 135.548 214.25 134.116 214.955C132.683 215.66 131.046 216.013 129.146 216.013H123.357V198.174H129.146C131.046 198.174 132.683 198.527 134.116 199.262ZM133.94 211.928C135.051 210.811 135.607 209.224 135.607 207.167C135.607 205.11 135.051 203.494 133.94 202.318C132.829 201.143 131.251 200.584 129.175 200.584H126.281V213.633H129.175C131.251 213.633 132.829 213.074 133.94 211.928Z" fill="#B3B3B3"></path>
<path d="M160.018 208.343C160.69 209.195 161.012 210.135 161.012 211.193C161.012 212.104 160.778 212.927 160.31 213.633C159.842 214.338 159.17 214.926 158.293 215.337C157.416 215.748 156.364 215.954 155.194 215.954H148.09V198.116H154.843C156.042 198.116 157.094 198.321 157.971 198.733C158.849 199.144 159.492 199.703 159.93 200.378C160.369 201.084 160.603 201.848 160.603 202.7C160.603 203.729 160.339 204.581 159.784 205.257C159.229 205.933 158.498 206.432 157.591 206.756C158.556 206.932 159.345 207.432 160.018 208.284V208.343ZM151.014 205.668H154.61C155.574 205.668 156.305 205.463 156.831 205.022C157.358 204.581 157.65 203.964 157.65 203.141C157.65 202.318 157.387 201.701 156.831 201.26C156.305 200.819 155.545 200.584 154.61 200.584H151.014V205.698V205.668ZM157.27 212.898C157.825 212.428 158.118 211.752 158.118 210.899C158.118 210.047 157.825 209.342 157.241 208.813C156.656 208.313 155.867 208.049 154.873 208.049H151.043V213.603H154.96C155.954 213.603 156.714 213.368 157.27 212.898Z" fill="#B3B3B3"></path>
<path d="M177.325 198.174L171.449 209.548V216.013H168.525V209.548L162.62 198.174H165.865L169.987 206.932L174.109 198.174H177.325Z" fill="#B3B3B3"></path>
<defs>
<clipPath id="clip0_56_97">
<rect width="369.626" height="148.433" fill="white" transform="translate(31.9331 0.472656)"></rect>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

17
public/svgs/linkding.svg Normal file
View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 450 450" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g transform="matrix(1,0,0,1,-70.3466,-70.3466)">
<g transform="matrix(1.18075,0,0,1.18075,-1257.39,-1386.74)">
<circle cx="1314.98" cy="1424.52" r="190.496" style="fill:rgb(88,86,224);"/>
</g>
<g transform="matrix(0.793058,0,0,0.793058,-739.034,-836.215)">
<g transform="matrix(0.707351,0.706862,-0.706862,0.707351,1331.93,-512.804)">
<path d="M1244.39,1293.95L1244.39,1493.59C1244.39,1493.59 1243.58,1561.48 1319.29,1562.47C1395.27,1563.46 1394.17,1493.59 1394.17,1493.59L1394.17,1293.95" style="fill:none;stroke:white;stroke-width:34.15px;"/>
</g>
<g transform="matrix(-0.710067,-0.704134,0.704134,-0.710067,1284.12,3366.41)">
<path d="M1244.39,1293.95L1244.39,1493.59C1244.39,1493.59 1243.58,1561.48 1319.29,1562.47C1395.27,1563.46 1394.17,1493.59 1394.17,1493.59L1394.17,1293.95" style="fill:none;stroke:white;stroke-width:34.15px;"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 164 KiB

317
public/svgs/seaweedfs.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 39 KiB

View file

Before

Width:  |  Height:  |  Size: 399 B

After

Width:  |  Height:  |  Size: 399 B

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="310" height="310">
<path d="M0 0 C9.60471507 -0.35947472 17.60138582 1.25577213 25.21484375 7.375 C31.0228154 12.92059922 34.28586353 19.34940078 35.04296875 27.328125 C35.26739532 37.42732066 32.22282336 43.70620706 25.25390625 51.015625 C19.13056619 56.52464939 12.71501524 58.199958 4.64648438 58.42651367 C-3.60132442 58.68234533 -10.664149 59.45742941 -18.16015625 63.1875 C-19.44986328 63.82171875 -19.44986328 63.82171875 -20.765625 64.46875 C-32.05491282 70.61722748 -38.18190681 79.9618587 -42.47265625 91.8125 C-45.26031265 105.18242487 -43.14714882 117.92066972 -36.09765625 129.5625 C-33.19688823 133.90120238 -29.99129092 137.93941032 -26.72265625 142 C-10.01842978 162.91159639 -1.6310923 189.59303298 -4.38720703 216.39550781 C-7.87059143 244.93837189 -21.32806065 270.53091306 -43.95068359 288.61914062 C-60.25734384 300.88532896 -81.58592714 309.68157633 -102.16015625 310.1875 C-103.19398437 310.23777344 -104.2278125 310.28804688 -105.29296875 310.33984375 C-114.91900688 310.61902119 -123.51783047 309.47119008 -131.16015625 303.1875 C-137.85971872 296.83446662 -141.12857879 290.5767799 -141.59765625 281.3125 C-141.35011335 273.75654752 -138.73165582 265.45783743 -133.16015625 260.1875 C-125.26768588 254.07620971 -118.278599 252.01949353 -108.41015625 251.8125 C-101.02424936 251.51451168 -94.85788523 250.52034122 -88.16015625 247.1875 C-87.30035156 246.7646875 -86.44054687 246.341875 -85.5546875 245.90625 C-74.26539968 239.75777252 -68.13840569 230.4131413 -63.84765625 218.5625 C-61.05999985 205.19257513 -63.17316368 192.45433028 -70.22265625 180.8125 C-73.12334718 176.47391292 -76.32831524 172.43492201 -79.59765625 168.375 C-96.64340752 147.10781417 -104.64377707 120.18483799 -101.94189453 92.99072266 C-99.23654649 71.97078644 -91.25889576 52.13178267 -77.16015625 36.1875 C-76.68320313 35.58035156 -76.20625 34.97320312 -75.71484375 34.34765625 C-60.1166509 14.69393326 -34.97796925 4.5532542 -10.8984375 0.66015625 C-7.26869812 0.27333731 -3.64202046 0.21609021 0 0 Z " fill="#8D54FE" transform="translate(204.16015625,-0.1875)"/>
<path d="M0 0 C9.60471507 -0.35947472 17.60138582 1.25577213 25.21484375 7.375 C31.0228154 12.92059922 34.28586353 19.34940078 35.04296875 27.328125 C35.26739532 37.42732066 32.22282336 43.70620706 25.25390625 51.015625 C19.13056619 56.52464939 12.71501524 58.199958 4.64648438 58.42651367 C-3.60132442 58.68234533 -10.664149 59.45742941 -18.16015625 63.1875 C-19.44986328 63.82171875 -19.44986328 63.82171875 -20.765625 64.46875 C-32.05491282 70.61722748 -38.18190681 79.9618587 -42.47265625 91.8125 C-45.103812 104.43182953 -43.47783179 117.16120467 -36.84765625 128.25 C-36.29078125 129.219375 -35.73390625 130.18875 -35.16015625 131.1875 C-35.49015625 132.1775 -35.82015625 133.1675 -36.16015625 134.1875 C-37.06894531 133.754375 -37.06894531 133.754375 -37.99609375 133.3125 C-46.54304091 129.52554495 -54.12763613 128.75195705 -62.9921875 132.078125 C-69.99738919 135.66276875 -75.66599982 141.70503071 -78.16015625 149.1875 C-78.30418973 150.52113703 -78.40606048 151.85968046 -78.4765625 153.19921875 C-78.51845703 153.95654297 -78.56035156 154.71386719 -78.60351562 155.49414062 C-78.64283203 156.27982422 -78.68214844 157.06550781 -78.72265625 157.875 C-78.76583984 158.67228516 -78.80902344 159.46957031 -78.85351562 160.29101562 C-78.95942993 162.25631447 -79.06028176 164.22188496 -79.16015625 166.1875 C-82.29464667 164.85543465 -83.81804526 163.68418029 -85.5625 160.765625 C-85.97556396 160.07694336 -86.38862793 159.38826172 -86.81420898 158.67871094 C-87.23774658 157.93911133 -87.66128418 157.19951172 -88.09765625 156.4375 C-88.53859619 155.67469727 -88.97953613 154.91189453 -89.43383789 154.12597656 C-103.30819328 129.4821243 -105.22178781 101.34055545 -98.1640625 74.3828125 C-93.84430859 60.24271839 -86.96441071 47.275144 -77.16015625 36.1875 C-76.68320313 35.58035156 -76.20625 34.97320312 -75.71484375 34.34765625 C-60.1166509 14.69393326 -34.97796925 4.5532542 -10.8984375 0.66015625 C-7.26869812 0.27333731 -3.64202046 0.21609021 0 0 Z " fill="#601BEB" transform="translate(204.16015625,-0.1875)"/>
<path d="M0 0 C9.48390772 6.98143123 16.79227036 15.71868955 19.3203125 27.40234375 C20.98440322 38.55791485 18.45362799 48.78994308 12.359375 58.23828125 C5.76541454 66.98537165 -3.94401642 72.54639433 -14.73046875 74.3046875 C-26.84092906 75.31089785 -36.99978407 72.16998719 -46.328125 64.4375 C-53.88878009 57.32671114 -58.80050209 48.17506949 -59.56640625 37.76171875 C-59.74936178 26.34224455 -57.0886514 18.68417846 -50.36328125 9.5234375 C-49.70328125 8.5334375 -49.04328125 7.5434375 -48.36328125 6.5234375 C-47.70328125 6.5234375 -47.04328125 6.5234375 -46.36328125 6.5234375 C-46.36328125 5.8634375 -46.36328125 5.2034375 -46.36328125 4.5234375 C-34.11073823 -6.10135222 -14.00407799 -8.74965534 0 0 Z " fill="#611CEB" transform="translate(227.36328125,69.4765625)"/>
<path d="M0 0 C0.86625 0.66 1.7325 1.32 2.625 2 C3.429375 2.598125 4.23375 3.19625 5.0625 3.8125 C13.27838377 10.82605931 16.5365354 20.70620634 17.90234375 31.11328125 C18.41780323 41.07195833 14.82882499 50.38487833 8.625 58 C8.12226562 58.62648438 7.61953125 59.25296875 7.1015625 59.8984375 C1.21264329 66.47679115 -8.14208116 71.65602581 -17 72.23828125 C-29.25196866 72.55218164 -38.6867624 71.04526191 -48.375 62.9140625 C-56.95930601 54.69861339 -61.4109544 44.72279591 -61.6875 32.875 C-61.57568611 21.8675441 -57.24858902 12.33539455 -49.78515625 4.28515625 C-35.76887255 -8.67578374 -16.03498481 -9.50221322 0 0 Z " fill="#8E55FF" transform="translate(115.375,172)"/>
<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C-3.94 6.94 -9.88 12.88 -16 19 C-16 13.77719767 -11.50919035 10.58625028 -8 7 C-5.39703481 4.58610154 -2.73530762 2.26244859 0 0 Z " fill="#8B58F0" transform="translate(141,22)"/>
<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C-0.31 3.31 -2.62 5.62 -5 8 C-5.66 7.67 -6.32 7.34 -7 7 C-4.66666667 4.66666667 -2.33333333 2.33333333 0 0 Z " fill="#A87CFF" transform="translate(75,257)"/>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -174,15 +174,34 @@
}
@utility menu-item {
@apply flex gap-3 items-center px-2 py-1 w-full text-sm sm:pr-0 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300 min-w-fit sm:min-w-64;
@apply flex gap-3 items-center px-2 py-1 w-full text-sm dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300 rounded-sm truncate min-w-0;
}
@utility menu-item-icon {
@apply flex-shrink-0 w-6 h-6 dark:hover:text-white;
}
@utility menu-item-label {
@apply min-w-0 flex-1 truncate;
}
@utility menu-item-active {
@apply text-black rounded-none dark:bg-coolgray-200 dark:text-warning bg-neutral-200;
@apply text-black rounded-sm dark:bg-coolgray-200 dark:text-warning bg-neutral-200 overflow-hidden;
}
@utility sub-menu-wrapper {
@apply flex flex-col items-start gap-2 min-w-40 sm:min-w-48 w-auto max-w-full sm:flex-shrink;
}
@utility sub-menu-item {
@apply flex gap-2 items-center px-2 py-1 w-full text-sm dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300 rounded-sm truncate min-w-0;
}
@utility sub-menu-item-icon {
@apply flex-shrink-0 w-4 h-4 dark:hover:text-white;
}
@utility heading-item-active {
@apply text-black rounded-none dark:bg-coolgray-200 dark:text-warning;
@apply text-black rounded-sm dark:bg-coolgray-200 dark:text-warning;
}
@utility icon {

View file

@ -73,21 +73,23 @@ $email = getOldOrLocal('email', 'test3@example.com');
</x-forms.button>
</form>
<div class="relative my-6">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-neutral-300 dark:border-coolgray-400"></div>
@if (!$isFirstUser)
<div class="relative my-6">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-neutral-300 dark:border-coolgray-400"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-gray-50 dark:bg-base text-neutral-500 dark:text-neutral-400">
Already have an account?
</span>
</div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-gray-50 dark:bg-base text-neutral-500 dark:text-neutral-400">
Already have an account?
</span>
</div>
</div>
<a href="/login"
class="block w-full text-center py-3 px-4 rounded-lg border border-neutral-300 dark:border-coolgray-400 font-medium hover:border-coollabs dark:hover:border-warning transition-colors">
{{ __('auth.already_registered') }}
</a>
<a href="{{ route('login') }}"
class="block w-full text-center py-3 px-4 rounded-lg border border-neutral-300 dark:border-coolgray-400 font-medium hover:border-coollabs dark:hover:border-warning transition-colors">
{{ __('auth.already_registered') }}
</a>
@endif
</div>
</div>
</div>

View file

@ -62,11 +62,11 @@
html {
font-size: 93.75%;
}
:root {
--vh: 1vh;
}
@media (min-width: 1024px) {
html {
font-size: 87.5%;
@ -107,19 +107,19 @@
<li>
<a title="Dashboard" href="/" {{ wireNavigate() }}
class="{{ request()->is('/') ? 'menu-item-active menu-item' : 'menu-item' }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24"
<svg xmlns="http://www.w3.org/2000/svg" class="menu-item-icon" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Dashboard
<span class="menu-item-label">Dashboard</span>
</a>
</li>
<li>
<a title="Projects" {{ wireNavigate() }}
class="{{ request()->is('project/*') || request()->is('projects') ? 'menu-item menu-item-active' : 'menu-item' }}"
href="/projects">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"
<svg xmlns="http://www.w3.org/2000/svg" class="menu-item-icon" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
@ -127,14 +127,14 @@
<path d="M4 12l8 4l8 -4" />
<path d="M4 16l8 4l8 -4" />
</svg>
Projects
<span class="menu-item-label">Projects</span>
</a>
</li>
<li>
<a title="Servers" {{ wireNavigate() }}
class="{{ request()->is('server/*') || request()->is('servers') ? 'menu-item menu-item-active' : 'menu-item' }}"
href="/servers">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"
<svg xmlns="http://www.w3.org/2000/svg" class="menu-item-icon" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
@ -145,7 +145,7 @@
<path d="M7 16v.01" />
<path d="M20 15l-2 3h3l-2 3" />
</svg>
Servers
<span class="menu-item-label">Servers</span>
</a>
</li>
@ -153,11 +153,11 @@
<a title="Sources" {{ wireNavigate() }}
class="{{ request()->is('source*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('source.all') }}">
<svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
<svg class="menu-item-icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
</svg>
Sources
<span class="menu-item-label">Sources</span>
</a>
</li>
<li>
@ -165,19 +165,19 @@
class="{{ request()->is('destination*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('destination.index') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" class="menu-item-icon" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2"
d="M9 4L3 8v12l6-3l6 3l6-4V4l-6 3l-6-3zm-2 8.001V12m4 .001V12m3-2l2 2m2 2l-2-2m0 0l2-2m-2 2l-2 2" />
</svg>
Destinations
<span class="menu-item-label">Destinations</span>
</a>
</li>
<li>
<a title="S3 Storages" {{ wireNavigate() }}
class="{{ request()->is('storages*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('storage.index') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" class="menu-item-icon" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M4 6a8 3 0 1 0 16 0A8 3 0 1 0 4 6" />
@ -185,14 +185,14 @@
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
</g>
</svg>
S3 Storages
<span class="menu-item-label">S3 Storages</span>
</a>
</li>
<li>
<a title="Shared variables" {{ wireNavigate() }}
class="{{ request()->is('shared-variables*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('shared-variables.index') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" class="menu-item-icon" viewBox="0 0 24 24">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path
@ -200,38 +200,38 @@
<path d="M8 16c1.5 0 3-2 4-3.5S14.5 9 16 9" />
</g>
</svg>
Shared Variables
<span class="menu-item-label">Shared Variables</span>
</a>
</li>
<li>
<a title="Notifications" {{ wireNavigate() }}
class="{{ request()->is('notifications*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('notifications.email') }}">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<svg class="menu-item-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2"
d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3H4a4 4 0 0 0 2-3v-3a7 7 0 0 1 4-6M9 17v1a3 3 0 0 0 6 0v-1" />
</svg>
Notifications
<span class="menu-item-label">Notifications</span>
</a>
</li>
<li>
<a title="Keys & Tokens" {{ wireNavigate() }}
class="{{ request()->is('security*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('security.private-key.index') }}">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<svg class="menu-item-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2"
d="m16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1-4.069 0l-.301-.301l-6.558 6.558a2 2 0 0 1-1.239.578L5.172 21H4a1 1 0 0 1-.993-.883L3 20v-1.172a2 2 0 0 1 .467-1.284l.119-.13L4 17h2v-2h2v-2l2.144-2.144l-.301-.301a2.877 2.877 0 0 1 0-4.069l2.643-2.643a2.877 2.877 0 0 1 4.069 0zM15 9h.01" />
</svg>
Keys & Tokens
<span class="menu-item-label">Keys & Tokens</span>
</a>
</li>
<li>
<a title="Tags" {{ wireNavigate() }}
class="{{ request()->is('tags*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('tags.show') }}">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<svg class="menu-item-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2">
<path
@ -239,7 +239,7 @@
<path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" />
</g>
</svg>
Tags
<span class="menu-item-label">Tags</span>
</a>
</li>
@can('canAccessTerminal')
@ -247,14 +247,14 @@
<a title="Terminal"
class="{{ request()->is('terminal*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('terminal') }}">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
<svg class="menu-item-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 7l5 5l-5 5" />
<path d="M12 19l7 0" />
</svg>
Terminal
<span class="menu-item-label">Terminal</span>
</a>
</li>
@endcan
@ -262,7 +262,7 @@
<a title="Profile" {{ wireNavigate() }}
class="{{ request()->is('profile*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('profile') }}">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
<svg class="menu-item-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
@ -270,14 +270,14 @@
<path d="M12 10m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" />
<path d="M6.168 18.849a4 4 0 0 1 3.832 -2.849h4a4 4 0 0 1 3.834 2.855" />
</svg>
Profile
<span class="menu-item-label">Profile</span>
</a>
</li>
<li>
<a title="Teams" {{ wireNavigate() }}
class="{{ request()->is('team*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('team.index') }}">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
<svg class="menu-item-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
@ -288,7 +288,7 @@
<path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M3 13v-1a2 2 0 0 1 2 -2h2" />
</svg>
Teams
<span class="menu-item-label">Teams</span>
</a>
</li>
@if (isCloud() && auth()->user()->isAdmin())
@ -296,12 +296,12 @@
<a title="Subscription" {{ wireNavigate() }}
class="{{ request()->is('subscription*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('subscription.show') }}">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<svg class="menu-item-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2"
d="M3 8a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3zm0 2h18M7 15h.01M11 15h2" />
</svg>
Subscription
<span class="menu-item-label">Subscription</span>
</a>
</li>
@endif
@ -311,7 +311,7 @@
<a title="Settings" {{ wireNavigate() }}
class="{{ request()->is('settings*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="/settings">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
<svg class="menu-item-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
@ -319,7 +319,7 @@
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
Settings
<span class="menu-item-label">Settings</span>
</a>
</li>
@endif
@ -328,12 +328,12 @@
@if (isInstanceAdmin() || session('impersonating'))
<li>
<a title="Admin" class="menu-item" href="/admin" {{ wireNavigate() }}>
<svg class="text-pink-500 icon" viewBox="0 0 256 256"
<svg class="text-pink-500 menu-item-icon" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M177.62 159.6a52 52 0 0 1-34 34a12.2 12.2 0 0 1-3.6.55a12 12 0 0 1-3.6-23.45a28 28 0 0 0 18.32-18.32a12 12 0 0 1 22.9 7.2ZM220 144a92 92 0 0 1-184 0c0-28.81 11.27-58.18 33.48-87.28a12 12 0 0 1 17.9-1.33l19.69 19.11L127 19.89a12 12 0 0 1 18.94-5.12C168.2 33.25 220 82.85 220 144m-24 0c0-41.71-30.61-78.39-52.52-99.29l-20.21 55.4a12 12 0 0 1-19.63 4.5L80.71 82.36C67 103.38 60 124.06 60 144a68 68 0 0 0 136 0" />
</svg>
Admin
<span class="menu-item-label">Admin</span>
</a>
</li>
@endif
@ -350,7 +350,7 @@
<a title="Onboarding"
class="{{ request()->is('onboarding*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('onboarding') }}">
<svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<svg class="menu-item-icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M224 128a8 8 0 0 1-8 8h-88a8 8 0 0 1 0-16h88a8 8 0 0 1 8 8m-96-56h88a8 8 0 0 0 0-16h-88a8 8 0 0 0 0 16m88 112h-88a8 8 0 0 0 0 16h88a8 8 0 0 0 0-16M82.34 42.34L56 68.69L45.66 58.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32m0 64L56 132.69l-10.34-10.35a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32m0 64L56 196.69l-10.34-10.35a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32 0l32-32a8 8 0 0 0-11.32-11.32" />
</svg>
@ -360,7 +360,7 @@
<li>
<a title="Sponsor us" class="menu-item" href="https://coolify.io/sponsorships"
target="_blank">
<svg class="text-pink-500 icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<svg class="text-pink-500 menu-item-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2">
<path d="M19.5 12.572L12 20l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 1 1 7.5 6.572" />
@ -368,7 +368,7 @@
d="M12 6L8.707 9.293a1 1 0 0 0 0 1.414l.543.543c.69.69 1.81.69 2.5 0l1-1a3.182 3.182 0 0 1 4.5 0l2.25 2.25m-7 3l2 2M15 13l2 2" />
</g>
</svg>
Sponsor us
<span class="menu-item-label">Sponsor us</span>
</a>
</li>
@endif
@ -380,11 +380,11 @@
<x-slot:content>
<div title="Send us feedback or get help!" class="cursor-pointer menu-item"
wire:click="help">
<svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<svg class="menu-item-icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M140 180a12 12 0 1 1-12-12a12 12 0 0 1 12 12M128 72c-22.06 0-40 16.15-40 36v4a8 8 0 0 0 16 0v-4c0-11 10.77-20 24-20s24 9 24 20s-10.77 20-24 20a8 8 0 0 0-8 8v8a8 8 0 0 0 16 0v-.72c18.24-3.35 32-17.9 32-35.28c0-19.85-17.94-36-40-36m104 56A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88" />
</svg>
Feedback
<span class="menu-item-label">Feedback</span>
</div>
</x-slot:content>
<livewire:help />
@ -394,11 +394,11 @@
<form action="/logout" method="POST">
@csrf
<button title="Logout" type="submit" class="gap-2 mb-6 menu-item">
<svg class="icon mr-1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<svg class="menu-item-icon mr-1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z" />
</svg>
Logout
<span>Logout</span>
</button>
</form>
</li>

View file

@ -2,12 +2,28 @@
'lastDeploymentInfo' => null,
'lastDeploymentLink' => null,
'resource' => null,
'projects' => null,
'environments' => null,
])
@php
$projects = auth()->user()->currentTeam()->projects()->get();
$environments = $resource->environment->project
use App\Models\Project;
// Use passed props if available, otherwise query (backwards compatible)
$projects = $projects ?? Project::ownedByCurrentTeamCached();
$environments = $environments ?? $resource->environment->project
->environments()
->with(['applications', 'services'])
->with([
'applications',
'services',
'postgresqls',
'redis',
'mongodbs',
'mysqls',
'mariadbs',
'keydbs',
'dragonflies',
'clickhouses',
])
->get();
$currentProjectUuid = data_get($resource, 'environment.project.uuid');
$currentEnvironmentUuid = data_get($resource, 'environment.uuid');
@ -74,6 +90,16 @@
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">
@foreach ($environments as $environment)
@php
// Use pre-loaded relations instead of databases() method to avoid N+1 queries
$envDatabases = collect()
->merge($environment->postgresqls ?? collect())
->merge($environment->redis ?? collect())
->merge($environment->mongodbs ?? collect())
->merge($environment->mysqls ?? collect())
->merge($environment->mariadbs ?? collect())
->merge($environment->keydbs ?? collect())
->merge($environment->dragonflies ?? collect())
->merge($environment->clickhouses ?? collect());
$envResources = collect()
->merge(
$environment->applications->map(
@ -81,9 +107,7 @@
),
)
->merge(
$environment
->databases()
->map(fn($db) => ['type' => 'database', 'resource' => $db]),
$envDatabases->map(fn($db) => ['type' => 'database', 'resource' => $db]),
)
->merge(
$environment->services->map(
@ -173,7 +197,9 @@
]),
};
$isCurrentResource = $res->uuid === $currentResourceUuid;
$resHasMultipleServers = $resType === 'application' && method_exists($res, 'additional_servers') && $res->additional_servers()->count() > 0;
// Use loaded relation count if available, otherwise check additional_servers_count attribute
$resHasMultipleServers = $resType === 'application' && method_exists($res, 'additional_servers') &&
($res->relationLoaded('additional_servers') ? $res->additional_servers->count() > 0 : ($res->additional_servers_count ?? 0) > 0);
$resServerName = $resHasMultipleServers ? null : data_get($res, 'destination.server.name');
@endphp
<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 @@
$isApplication = $resourceType === 'App\Models\Application';
$isService = $resourceType === 'App\Models\Service';
$isDatabase = str_contains($resourceType, 'Database') || str_contains($resourceType, 'Standalone');
$hasMultipleServers = $isApplication && method_exists($resource, 'additional_servers') && $resource->additional_servers()->count() > 0;
// Use loaded relation count if available, otherwise check additional_servers_count attribute
$hasMultipleServers = $isApplication && method_exists($resource, 'additional_servers') &&
($resource->relationLoaded('additional_servers') ? $resource->additional_servers->count() > 0 : ($resource->additional_servers_count ?? 0) > 0);
$serverName = $hasMultipleServers ? null : data_get($resource, 'destination.server.name');
$routeParams = [
'project_uuid' => $currentProjectUuid,

View file

@ -1,16 +1,16 @@
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class="{{ request()->routeIs('server.proxy') ? 'menu-item menu-item-active' : 'menu-item' }}" {{ wireNavigate() }}
<div class="sub-menu-wrapper">
<a class="{{ request()->routeIs('server.proxy') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}" {{ wireNavigate() }}
href="{{ route('server.proxy', $parameters) }}">
<button>Configuration</button>
<span class="menu-item-label">Configuration</span>
</a>
@if ($server->proxySet())
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'menu-item menu-item-active' : 'menu-item' }}" {{ wireNavigate() }}
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}" {{ wireNavigate() }}
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
<button>Dynamic Configurations</button>
<span class="menu-item-label">Dynamic Configurations</span>
</a>
<a class="{{ request()->routeIs('server.proxy.logs') ? 'menu-item menu-item-active' : 'menu-item' }}"
<a class="{{ request()->routeIs('server.proxy.logs') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}"
href="{{ route('server.proxy.logs', $parameters) }}">
<button>Logs</button>
<span class="menu-item-label">Logs</span>
</a>
@endif
</div>

View file

@ -1,10 +1,10 @@
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class="{{ request()->routeIs('server.security.patches') ? 'menu-item menu-item-active' : 'menu-item' }}" {{ wireNavigate() }}
<div class="sub-menu-wrapper">
<a class="{{ request()->routeIs('server.security.patches') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}" {{ wireNavigate() }}
href="{{ route('server.security.patches', $parameters) }}">
Server Patching
<span class="menu-item-label">Server Patching</span>
</a>
<a class="{{ request()->routeIs('server.security.terminal-access') ? 'menu-item menu-item-active' : 'menu-item' }}"
<a class="{{ request()->routeIs('server.security.terminal-access') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}"
href="{{ route('server.security.terminal-access', $parameters) }}">
Terminal Access
<span class="menu-item-label">Terminal Access</span>
</a>
</div>

View file

@ -1,53 +1,51 @@
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class="menu-item {{ $activeMenu === 'general' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.show', ['server_uuid' => $server->uuid]) }}">General</a>
<div class="sub-menu-wrapper">
<a class="sub-menu-item {{ $activeMenu === 'general' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.show', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">General</span></a>
@if ($server->isFunctional())
<a class="menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.advanced', ['server_uuid' => $server->uuid]) }}">Advanced
<a class="sub-menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.advanced', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Advanced</span>
</a>
@endif
@if ($server->isFunctional() && !$server->isSwarm() && !$server->isBuildServer())
<a class="menu-item {{ $activeMenu === 'sentinel' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.sentinel', ['server_uuid' => $server->uuid]) }}">Sentinel
<a class="sub-menu-item {{ $activeMenu === 'sentinel' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.sentinel', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Sentinel</span>
</a>
@endif
<a class="menu-item {{ $activeMenu === 'private-key' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}">Private Key
<a class="sub-menu-item {{ $activeMenu === 'private-key' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Private Key</span>
</a>
@if ($server->hetzner_server_id)
<a class="menu-item {{ $activeMenu === 'cloud-provider-token' ? 'menu-item-active' : '' }}"
<a class="sub-menu-item {{ $activeMenu === 'cloud-provider-token' ? 'menu-item-active' : '' }}"
{{ wireNavigate() }}
href="{{ route('server.cloud-provider-token', ['server_uuid' => $server->uuid]) }}">Hetzner Token
href="{{ route('server.cloud-provider-token', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Hetzner Token</span>
</a>
@endif
<a class="menu-item {{ $activeMenu === 'ca-certificate' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.ca-certificate', ['server_uuid' => $server->uuid]) }}">CA Certificate
<a class="sub-menu-item {{ $activeMenu === 'ca-certificate' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.ca-certificate', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">CA Certificate</span>
</a>
@if (!$server->isLocalhost())
<a class="menu-item {{ $activeMenu === 'cloudflare-tunnel' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.cloudflare-tunnel', ['server_uuid' => $server->uuid]) }}">Cloudflare
Tunnel</a>
<a class="sub-menu-item {{ $activeMenu === 'cloudflare-tunnel' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.cloudflare-tunnel', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Cloudflare Tunnel</span></a>
@endif
@if ($server->isFunctional())
<a class="menu-item {{ $activeMenu === 'docker-cleanup' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.docker-cleanup', ['server_uuid' => $server->uuid]) }}">Docker Cleanup
<a class="sub-menu-item {{ $activeMenu === 'docker-cleanup' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.docker-cleanup', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Docker Cleanup</span>
</a>
<a class="menu-item {{ $activeMenu === 'destinations' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.destinations', ['server_uuid' => $server->uuid]) }}">Destinations
<a class="sub-menu-item {{ $activeMenu === 'destinations' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.destinations', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Destinations</span>
</a>
<a class="menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.log-drains', ['server_uuid' => $server->uuid]) }}">Log
Drains</a>
<a class="menu-item {{ $activeMenu === 'metrics' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.charts', ['server_uuid' => $server->uuid]) }}">Metrics</a>
<a class="sub-menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.log-drains', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Log Drains</span></a>
<a class="sub-menu-item {{ $activeMenu === 'metrics' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.charts', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Metrics</span></a>
@endif
@if (!$server->isBuildServer() && !$server->settings->is_cloudflare_tunnel)
<a class="menu-item {{ $activeMenu === 'swarm' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.swarm', ['server_uuid' => $server->uuid]) }}">Swarm (experimental)
<a class="sub-menu-item {{ $activeMenu === 'swarm' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.swarm', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Swarm (experimental)</span>
</a>
@endif
@if (!$server->isLocalhost())
<a class="menu-item {{ $activeMenu === 'danger' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.delete', ['server_uuid' => $server->uuid]) }}">Danger</a>
<a class="sub-menu-item {{ $activeMenu === 'danger' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.delete', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Danger</span></a>
@endif
</div>

View file

@ -4,21 +4,24 @@
'isImportSupported' => false,
])
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class="menu-item"
<div class="sub-menu-wrapper">
<a class="sub-menu-item"
class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-active' : '' }}"
{{ wireNavigate() }}
href="{{ route('project.service.configuration', [...$parameters, 'stack_service_uuid' => null]) }}">
<button><- Back</button>
<svg xmlns="http://www.w3.org/2000/svg" class="sub-menu-item-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
<span class="menu-item-label">Back</span>
</a>
<a class="menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.index', $parameters) }}">General</a>
<a class="sub-menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.index', $parameters) }}"><span class="menu-item-label">General</span></a>
@if ($serviceDatabase?->isBackupSolutionAvailable() || $serviceDatabase?->is_migrated)
<a class="menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.database.backups', $parameters) }}">Backups</a>
<a class="sub-menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.database.backups', $parameters) }}"><span class="menu-item-label">Backups</span></a>
@endif
@if ($isImportSupported)
<a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.service.database.import', $parameters) }}">Import Backup</a>
<a class="sub-menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.service.database.import', $parameters) }}"><span class="menu-item-label">Import Backup</span></a>
@endif
</div>

View file

@ -1,8 +1,8 @@
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class="menu-item {{ $activeMenu === 'general' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('settings.index') }}">General</a>
<a class="menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('settings.advanced') }}">Advanced</a>
<a class="menu-item {{ $activeMenu === 'updates' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('settings.updates') }}">Updates</a>
<div class="sub-menu-wrapper">
<a class="sub-menu-item {{ $activeMenu === 'general' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('settings.index') }}"><span class="menu-item-label">General</span></a>
<a class="sub-menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('settings.advanced') }}"><span class="menu-item-label">Advanced</span></a>
<a class="sub-menu-item {{ $activeMenu === 'updates' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('settings.updates') }}"><span class="menu-item-label">Updates</span></a>
</div>

View file

@ -22,7 +22,7 @@
<div class="relative z-50 lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-black/80" x-on:click="open = false"></div>
<div class="fixed inset-y-0 right-0 h-full flex">
<div class="relative flex flex-1 w-full max-w-56 ">
<div class="relative flex flex-1 w-full max-w-56 min-w-0">
<div class="absolute top-0 flex justify-center w-16 pt-5 right-full">
<button type="button" class="-m-2.5 p-2.5" x-on:click="open = !open">
<span class="sr-only">Close sidebar</span>
@ -33,15 +33,15 @@
</button>
</div>
<div class="flex flex-col pb-2 overflow-y-auto min-w-56 dark:bg-coolgray-100 gap-y-5 scrollbar">
<div class="flex flex-col pb-2 overflow-y-auto min-w-56 dark:bg-coolgray-100 gap-y-5 scrollbar min-w-0">
<x-navbar />
</div>
</div>
</div>
</div>
<div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-56 lg:flex-col">
<div class="flex flex-col overflow-y-auto grow gap-y-5 scrollbar">
<div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-56 lg:flex-col min-w-0">
<div class="flex flex-col overflow-y-auto grow gap-y-5 scrollbar min-w-0">
<x-navbar />
</div>
</div>

View file

@ -7,29 +7,25 @@
<livewire:project.application.heading :application="$application" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">General</a>
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.advanced', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Advanced</a>
<div class="sub-menu-wrapper">
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">General</span></a>
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.advanced', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Advanced</span></a>
@if ($application->destination->server->isSwarm())
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Swarm
Configuration</a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Swarm Configuration</span></a>
@endif
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Environment
Variables</a>
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Persistent
Storage</a>
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Environment Variables</span></a>
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Persistent Storage</span></a>
@if ($application->git_based())
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.source', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Git
Source</a>
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.source', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Git Source</span></a>
@endif
<a class="menu-item flex items-center gap-2" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Servers
<a class="sub-menu-item flex items-center gap-2" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Servers</span>
@if ($application->server_status == false)
<span title="One or more servers are unreachable or misconfigured.">
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
@ -46,37 +42,32 @@
</span>
@endif
</a>
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Scheduled
Tasks</a>
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Webhooks</a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Scheduled Tasks</span></a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Webhooks</span></a>
@if ($application->deploymentType() !== 'deploy_key')
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.preview-deployments', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Preview
Deployments</a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.preview-deployments', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Preview Deployments</span></a>
@endif
@if ($application->build_pack !== 'dockercompose')
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.healthcheck', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Healthcheck</a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.healthcheck', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Healthcheck</span></a>
@endif
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.rollback', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Rollback</a>
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Resource
Limits</a>
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Resource
Operations</a>
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Metrics</a>
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Tags</a>
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Danger
Zone</a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.rollback', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Rollback</span></a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Resource Limits</span></a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Resource Operations</span></a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Metrics</span></a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Tags</span></a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.application.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Danger Zone</span></a>
</div>
<div class="w-full">
<div class="w-full sm:flex-grow">
@if ($currentRoute === 'project.application.configuration')
<livewire:project.application.general :application="$application" />
@elseif ($currentRoute === 'project.application.swarm' && $application->destination->server->isSwarm())

View file

@ -6,37 +6,31 @@
<livewire:project.shared.configuration-checker :resource="$database" />
<livewire:project.database.heading :database="$database" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">General</a>
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Environment
Variables</a>
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Servers</a>
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Persistent
Storage</a>
<div class="sub-menu-wrapper">
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">General</span></a>
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Environment Variables</span></a>
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Servers</span></a>
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Persistent Storage</span></a>
@can('update', $database)
<a class='menu-item' wire:current.exact="menu-item-active"
href="{{ route('project.database.import-backup', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Import
Backup</a>
<a class='sub-menu-item' wire:current.exact="menu-item-active"
href="{{ route('project.database.import-backup', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Import Backup</span></a>
@endcan
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Webhooks</a>
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Resource
Limits</a>
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Resource
Operations</a>
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Metrics</a>
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Tags</a>
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Danger
Zone</a>
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Webhooks</span></a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Resource Limits</span></a>
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Resource Operations</span></a>
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Metrics</span></a>
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Tags</span></a>
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
href="{{ route('project.database.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Danger Zone</span></a>
</div>
<div class="w-full">
@if ($currentRoute === 'project.database.configuration')

View file

@ -14,7 +14,7 @@
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2 -mt-1">
@foreach ($projects as $project)
<div class="relative gap-2 cursor-pointer coolbox group">
<a href="{{ $project->navigateTo() }}" class="absolute inset-0"></a>
<a href="{{ $project->navigateTo() }}" {{ wireNavigate() }} class="absolute inset-0"></a>
<div class="flex flex-1 mx-6">
<div class="flex flex-col justify-center flex-1">
<div class="box-title">{{ $project->name }}</div>

View file

@ -29,9 +29,6 @@
<livewire:project.delete-environment :disabled="!$environment->isEmpty()" :environment_id="$environment->id" />
@endcan
</div>
@php
$projects = auth()->user()->currentTeam()->projects()->get();
@endphp
<nav class="flex pt-2 pb-6">
<ol class="flex items-center">
<li class="inline-flex items-center" x-data="{ projectOpen: false, toggle() { this.projectOpen = !this.projectOpen }, open() { this.projectOpen = true }, close() { this.projectOpen = false } }">
@ -53,7 +50,7 @@
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
class="absolute z-20 top-full mt-1 w-56 -ml-2 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">
@foreach ($projects as $proj)
@foreach ($allProjects as $proj)
<a href="{{ route('project.show', ['project_uuid' => $proj->uuid]) }}"
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 {{ $proj->uuid === $project->uuid ? 'dark:text-warning font-semibold' : '' }}"
title="{{ $proj->name }}">
@ -63,12 +60,6 @@
</div>
</div>
</li>
@php
$allEnvironments = $project
->environments()
->with(['applications', 'services'])
->get();
@endphp
<li class="inline-flex items-center" x-data="{ envOpen: false, activeEnv: null, envPositions: {}, activeRes: null, resPositions: {}, activeMenuEnv: null, menuPositions: {}, closeTimeout: null, envTimeout: null, resTimeout: null, menuTimeout: null, toggle() { this.envOpen = !this.envOpen; if (!this.envOpen) { this.activeEnv = null;
this.activeRes = null;
this.activeMenuEnv = null; } }, open() { clearTimeout(this.closeTimeout);
@ -162,6 +153,16 @@
<!-- Resources Sub-dropdown (2nd level) -->
@foreach ($allEnvironments as $env)
@php
// Use pre-loaded relations instead of databases() method to avoid N+1 queries
$envDatabases = collect()
->merge($env->postgresqls ?? collect())
->merge($env->redis ?? collect())
->merge($env->mongodbs ?? collect())
->merge($env->mysqls ?? collect())
->merge($env->mariadbs ?? collect())
->merge($env->keydbs ?? collect())
->merge($env->dragonflies ?? collect())
->merge($env->clickhouses ?? collect());
$envResources = collect()
->merge(
$env->applications->map(
@ -169,9 +170,7 @@
),
)
->merge(
$env
->databases()
->map(fn($db) => ['type' => 'database', 'resource' => $db]),
$envDatabases->map(fn($db) => ['type' => 'database', 'resource' => $db]),
)
->merge(
$env->services->map(fn($svc) => ['type' => 'service', 'resource' => $svc]),
@ -208,10 +207,11 @@
'database_uuid' => $res->uuid,
]),
};
// Use loaded relation to check additional_servers (avoids N+1 query)
$resHasMultipleServers =
$resType === 'application' &&
method_exists($res, 'additional_servers') &&
$res->additional_servers()->count() > 0;
($res->relationLoaded('additional_servers') ? $res->additional_servers->count() > 0 : false);
$resServerName = $resHasMultipleServers
? null
: data_get($res, 'destination.server.name');

View file

@ -5,32 +5,27 @@
<livewire:project.service.heading :service="$service" :parameters="$parameters" :query="$query" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class="menu-item sm:min-w-fit" target="_blank" href="{{ $service->documentation() }}">Documentation
<div class="sub-menu-wrapper">
<a class="sub-menu-item" target="_blank" href="{{ $service->documentation() }}"><span class="menu-item-label">Documentation</span>
<x-external-link /></a>
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">General</a>
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Environment
Variables</a>
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.storages', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Persistent
Storages</a>
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Scheduled
Tasks</a>
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Webhooks</a>
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Resource
Operations</a>
<a class='sub-menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">General</span></a>
<a class='sub-menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">Environment Variables</span></a>
<a class='sub-menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.storages', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">Persistent Storages</span></a>
<a class='sub-menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">Scheduled Tasks</span></a>
<a class='sub-menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">Webhooks</span></a>
<a class='sub-menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">Resource Operations</span></a>
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Tags</a>
<a class='sub-menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">Tags</span></a>
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Danger
Zone</a>
<a class='sub-menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
href="{{ route('project.service.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">Danger Zone</span></a>
</div>
<div class="w-full">
@if ($currentRoute === 'project.service.configuration')

View file

@ -4,14 +4,17 @@
@if ($resourceType === 'database')
<x-service-database.sidebar :parameters="$parameters" :serviceDatabase="$serviceDatabase" :isImportSupported="$isImportSupported" />
@else
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class="menu-item"
<div class="sub-menu-wrapper">
<a class="sub-menu-item"
class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-active' : '' }}"
{{ wireNavigate() }}
href="{{ route('project.service.configuration', [...$parameters, 'stack_service_uuid' => null]) }}">
<button><- Back</button>
<svg xmlns="http://www.w3.org/2000/svg" class="sub-menu-item-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
<span class="menu-item-label">Back</span>
</a>
<a class="menu-item menu-item-active" href="#">General</a>
<a class="sub-menu-item menu-item-active" href="#"><span class="menu-item-label">General</span></a>
</div>
@endif
<div class="w-full">

View file

@ -2,8 +2,7 @@
<div class="flex flex-col gap-4">
@if ($resource->type() === 'service' || data_get($resource, 'build_pack') === 'dockercompose')
<div class="w-full p-2 text-sm rounded bg-warning/10 text-warning">
Volume mounts are read-only. If you would like to add or modify a volume, you must edit your Docker
Compose file and reload the compose file.
For docker compose based applications Volume mounts are read-only in the Coolify dashboard. To add, modify, or manage volumes, you must edit your Docker Compose file and reload the compose file.
</div>
@endif
@foreach ($resource->persistentStorages as $storage)

View file

@ -416,4 +416,4 @@
}
}))
})
</script>
</script>

View file

@ -103,6 +103,10 @@ Route::group([
Route::post('/applications/private-deploy-key', [ApplicationsController::class, 'create_private_deploy_key_application'])->middleware(['api.ability:write']);
Route::post('/applications/dockerfile', [ApplicationsController::class, 'create_dockerfile_application'])->middleware(['api.ability:write']);
Route::post('/applications/dockerimage', [ApplicationsController::class, 'create_dockerimage_application'])->middleware(['api.ability:write']);
/**
* @deprecated Use POST /api/v1/services instead. This endpoint creates a Service, not an Application and is a unstable duplicate of POST /api/v1/services.
*/
Route::post('/applications/dockercompose', [ApplicationsController::class, 'create_dockercompose_application'])->middleware(['api.ability:write']);
Route::get('/applications/{uuid}', [ApplicationsController::class, 'application_by_uuid'])->middleware(['api.ability:read']);

View file

@ -6,7 +6,7 @@
services:
autobase:
image: autobase/console_ui:2.4.1
image: autobase/console_ui:2.5.2
platform: linux/amd64
environment:
- SERVICE_FQDN_AUTOBASE_80
@ -22,7 +22,7 @@ services:
condition: service_healthy
autobase-db:
image: autobase/console_db:2.4.1
image: autobase/console_db:2.5.2
platform: linux/amd64
environment:
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
@ -35,7 +35,7 @@ services:
retries: 10
autobase-api:
image: autobase/console_api:2.4.1
image: autobase/console_api:2.5.2
platform: linux/amd64
environment:
- PG_CONSOLE_DB_HOST=autobase-db

View file

@ -0,0 +1,80 @@
# documentation: https://chibisafe.app/docs/intro
# slogan: A beautiful and performant vault to save all your files in the cloud.
# tags: storage,file-sharing,upload,sharing
# logo: svgs/chibisafe.svg
# port: 80
## This service includes its own reverse proxy because the application routing is broken, also it needs caddy for serving static files https://github.com/coollabsio/coolify/pull/5808#pullrequestreview-3340419006
services:
chibisafe:
image: chibisafe/chibisafe:v6.5.5
environment:
- BASE_API_URL=http://chibisafe-server:8000
healthcheck:
test: ["CMD", "wget", "--spider", "--quiet", "http://127.0.0.1:8001"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
chibisafe-server:
image: chibisafe/chibisafe-server:v6.5.5
volumes:
- chibisafe-database:/app/database:rw
- chibisafe-uploads:/app/uploads:rw
- chibisafe-logs:/app/logs:rw
healthcheck:
test:
[
"CMD",
"wget",
"--spider",
"--quiet",
"http://127.0.0.1:8000/api/health",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
caddy:
image: caddy:2-alpine
environment:
- SERVICE_URL_CHIBISAFE_80
healthcheck:
test: ["CMD", "wget", "--spider", "--quiet", "http://127.0.0.1:80"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
volumes:
- uploads:/app/uploads:ro
- type: bind
source: ./Caddyfile
target: /etc/caddy/Caddyfile
content: |
# chibisafe/Caddyfile
# https://chibisafe.moe/guides/running-with-docker
:80 {
route {
file_server * {
root /app/uploads
pass_thru
}
@api path /api/*
reverse_proxy @api http://chibisafe-server:8000 {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up X-Real-IP {http.request.header.X-Real-IP}
}
@docs path /docs*
reverse_proxy @docs http://chibisafe-server:8000 {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up X-Real-IP {http.request.header.X-Real-IP}
}
reverse_proxy http://chibisafe:8001 {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up X-Real-IP {http.request.header.X-Real-IP}
}
}
}

View file

@ -0,0 +1,51 @@
# documentation: https://help.glpi-project.org/documentation
# slogan: GLPI (Gestionnaire Libre de Parc Informatique) is a free, open-source IT Service Management (ITSM) platform used for IT asset management, helpdesk, and service desk operations.
# category: helpdesk
# tags: helpdesk, ticketing, support, open-source
# logo: svgs/glpi.svg
# port: 80
services:
glpi:
image: glpi/glpi:11
environment:
- SERVICE_URL_GLPI_80
- GLPI_DB_HOST=glpi-db
- GLPI_DB_PORT=3306
- GLPI_DB_NAME=${MYSQL_DATABASE:-glpi-db}
- GLPI_DB_USER=${SERVICE_USER_MYSQL}
- GLPI_DB_PASSWORD=${SERVICE_PASSWORD_MYSQL}
volumes:
- glpi-data:/var/glpi:rw
depends_on:
glpi-db:
condition: service_healthy
healthcheck:
test:
- CMD
- curl
- "-f"
- "http://localhost/"
interval: 30s
timeout: 10s
retries: 3
glpi-db:
image: mysql:8
volumes:
- mysql-data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_ROOT}
- MYSQL_DATABASE=${MYSQL_DATABASE:-glpi-db}
- MYSQL_USER=${SERVICE_USER_MYSQL}
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
healthcheck:
test:
- CMD
- mysqladmin
- ping
- "-h"
- 127.0.0.1
interval: 5s
timeout: 20s
retries: 10

View file

@ -1,5 +1,5 @@
# documentation: https://docs.hatchet.run/self-hosting/docker-compose
# slogan: Hatchet is a high-throughput, low-latency computing service. It's built on an open-source, fault-tolerant queue, allowing work to be delivered as fast as your system can handle
# slogan: Hatchet allows you to run background tasks at scale with a high-throughput, low-latency computing service built on an open-source, fault-tolerant queue.
# tags: ai-agents,background-tasks,data-pipelines,scheduling
# logo: svgs/hatchet.svg
# port: 80

View file

@ -0,0 +1,20 @@
# documentation: https://linkding.link/
# slogan: A self-hosted bookmark manager designed to be minimal, fast, and easy to set up. (Includes feature for archiving websites as HTML snapshots)
# category: RSS
# tags: rss, feed
# logo: svgs/linkding.svg
# port: 9090
services:
linkding-plus:
image: sissbruecker/linkding:latest-plus
volumes:
- 'linkding_data:/etc/linkding/data'
environment:
- SERVICE_URL_LINKDING_9090
- LD_SUPERUSER_NAME=${SERVICE_USER_LINKDING}
- LD_SUPERUSER_PASSWORD=${SERVICE_PASSWORD_LINKDING}
healthcheck:
test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/9090' || exit 1"]
interval: 5s
timeout: 5s

View file

@ -0,0 +1,20 @@
# documentation: https://linkding.link/
# slogan: A self-hosted bookmark manager designed to be minimal, fast, and easy to set up.
# category: RSS
# tags: rss, feed
# logo: svgs/linkding.svg
# port: 9090
services:
linkding:
image: sissbruecker/linkding:latest
volumes:
- 'linkding_data:/etc/linkding/data'
environment:
- SERVICE_URL_LINKDING_9090
- LD_SUPERUSER_NAME=${SERVICE_USER_LINKDING}
- LD_SUPERUSER_PASSWORD=${SERVICE_PASSWORD_LINKDING}
healthcheck:
test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/9090' || exit 1"]
interval: 5s
timeout: 5s

View file

@ -0,0 +1,87 @@
# documentation: https://docs.openarchiver.com/
# slogan: A self-hosted, open-source email archiving solution with full-text search capability.
# tags: email archiving,email,compliance,search
# logo: svgs/openarchiver.svg
# port: 3000
services:
open-archiver:
image: logiclabshq/open-archiver:latest
environment:
- SERVICE_URL_OPENARCHIVER_3000
- ENCRYPTION_KEY=${SERVICE_HEX_32_ENCRYPTIONKEY}
- STORAGE_ENCRYPTION_KEY=${SERVICE_HEX_32_STORAGEENCRYPTIONKEY}
- PORT_BACKEND=${PORT_BACKEND:-4000}
- PORT_FRONTEND=${PORT_FRONTEND:-3000}
- NODE_ENV=${NODE_ENV:-production}
- SYNC_FREQUENCY=${SYNC_FREQUENCY:-* * * * *}
- POSTGRES_DB=${POSTGRES_DB:-open_archive}
- POSTGRES_USER=${SERVICE_USER_POSTGRES}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-open-archiver-db}
- MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILISEARCH}
- MEILI_HOST=http://meilisearch:7700
- REDIS_HOST=valkey
- REDIS_PORT=6379
- REDIS_PASSWORD=${SERVICE_PASSWORD_VALKEY}
- REDIS_TLS_ENABLED=false
- STORAGE_TYPE=${STORAGE_TYPE:-local}
- STORAGE_LOCAL_ROOT_PATH=${STORAGE_LOCAL_ROOT_PATH:-/var/data/open-archiver}
- BODY_SIZE_LIMIT=${BODY_SIZE_LIMIT:-100M}
- STORAGE_S3_ENDPOINT=${STORAGE_S3_ENDPOINT}
- STORAGE_S3_BUCKET=${STORAGE_S3_BUCKET}
- STORAGE_S3_ACCESS_KEY_ID=${STORAGE_S3_ACCESS_KEY_ID}
- STORAGE_S3_SECRET_ACCESS_KEY=${STORAGE_S3_SECRET_ACCESS_KEY}
- STORAGE_S3_REGION=${STORAGE_S3_REGION}
- STORAGE_S3_FORCE_PATH_STYLE=${STORAGE_S3_FORCE_PATH_STYLE:-false}
- JWT_SECRET=${SERVICE_BASE64_128_JWT}
- JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-7d}
- RATE_LIMIT_WINDOW_MS=${RATE_LIMIT_WINDOW_MS:-60000}
- RATE_LIMIT_MAX_REQUESTS=${RATE_LIMIT_MAX_REQUESTS:-100}
volumes:
- archiver-data:/var/data/open-archiver
depends_on:
postgres:
condition: service_healthy
valkey:
condition: service_healthy
meilisearch:
condition: service_healthy
postgres:
image: postgres:17-alpine
environment:
- POSTGRES_DB=${POSTGRES_DB:-open-archiver-db}
- POSTGRES_USER=${SERVICE_USER_POSTGRES}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- LC_ALL=C
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 20s
retries: 10
valkey:
image: valkey/valkey:8-alpine
command: valkey-server --requirepass ${SERVICE_PASSWORD_VALKEY}
volumes:
- valkey-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
meilisearch:
image: getmeili/meilisearch:v1.15
environment:
- MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILISEARCH}
volumes:
- meilisearch-data:/meili_data
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:7700/health"]
interval: 2s
timeout: 10s
retries: 15

View file

@ -1,5 +1,5 @@
# documentation: https://www.redmine.org/
# slogan: Redmine is a flexible project management web application. Written using the Ruby on Rails framework, it is cross-platform and cross-database.
# slogan: Redmine is a flexible project management web application.
# tags: redmine,project management
# logo: svgs/redmine.svg
# port: 3000

View file

@ -2,7 +2,7 @@
# slogan: SeaweedFS is a simple and highly scalable distributed file system. Compatible with S3, with an admin web interface.
# category: storage
# tags: object, storage, server, s3, api
# logo: svgs/garage.svg
# logo: svgs/seaweedfs.svg
# port: 8333
services:

View file

@ -0,0 +1,22 @@
# documentation: https://github.com/marckohlbrugge/sessy/blob/main/docs/docker-deployment.md
# slogan: Email observability platform for monitoring and analyzing email systems.
# category: monitoring
# tags: email, observability, monitoring, analytics
# logo: svgs/sessy.svg
# port: 80
services:
sessy:
image: ghcr.io/marckohlbrugge/sessy:main
environment:
- SERVICE_URL_SESSY_80
- SECRET_KEY_BASE=$SERVICE_HEX_64_SESSYSECRET
- HTTP_AUTH_USERNAME=$SERVICE_USER_SESSY
- HTTP_AUTH_PASSWORD=$SERVICE_PASSWORD_SESSY
volumes:
- sessy-data:/rails/storage
healthcheck:
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:80 || [ $? -eq 22 ]"]
interval: 5s
timeout: 10s
retries: 10

View file

@ -13,7 +13,7 @@ services:
- SERVICE_FQDN_SFTPGO_8080
- SFTPGO_DATA_PROVIDER__DRIVER=postgresql
- SFTPGO_DATA_PROVIDER__CONNECTION_STRING=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_64_POSTGRES}@postgresql:5432/${POSTGRES_DATABASE:-sftpgo-db}
- SFTPGO_SFTPD__BINDINGS__0__PORT=2022
- SFTPGO_SFTPD__BINDINGS__0__PORT=${PORT_SFTPGO:-2222}
- SFTPGO_LOG_LEVEL=${LOG_LEVEL:-info}
healthcheck:
test:

View file

@ -450,7 +450,7 @@ services:
- SIGNOZ_ALERTMANAGER_SIGNOZ_GLOBAL_SMTP__AUTH__USERNAME=${SIGNOZ_ALERTMANAGER_SIGNOZ_GLOBAL_SMTP__AUTH__USERNAME}
- SIGNOZ_ALERTMANAGER_SIGNOZ_GLOBAL_SMTP__FROM=${SIGNOZ_ALERTMANAGER_SIGNOZ_GLOBAL_SMTP__FROM}
- SIGNOZ_ALERTMANAGER_SIGNOZ_GLOBAL_SMTP__SMARTHOST=${SIGNOZ_ALERTMANAGER_SIGNOZ_GLOBAL_SMTP__SMARTHOST}
- DOT_METRICS_ENABELD=true
- DOT_METRICS_ENABLED=true
healthcheck:
test:
- CMD

View file

@ -0,0 +1,32 @@
# documentation: https://github.com/rahulhaque/soketi-app-manager-filament
# slogan: Manage soketi websocket server and apps with ease.
# tags: soketi,websockets,app-manager,dashboard
# logo: svgs/soketi-app-manager.svg
# port: 8080
services:
soketi-app-manager:
image: ghcr.io/rahulhaque/soketi-app-manager-filament-alpine:latest
environment:
- SERVICE_FQDN_SOKETIAPPMANAGER_8080
- AUTORUN_ENABLED=true
- AUTORUN_LARAVEL_MIGRATION=${AUTORUN_LARAVEL_MIGRATION:-true}
- APP_DEBUG=${APP_DEBUG:-false}
- APP_URL=$SERVICE_FQDN_SOKETIAPPMANAGER
- APP_KEY=$APP_KEY
- DB_CONNECTION=$DB_CONNECTION
- DB_HOST=$DB_HOST
- DB_PORT=$DB_PORT
- DB_DATABASE=$DB_DATABASE
- DB_USERNAME=$DB_USERNAME
- DB_PASSWORD=$DB_PASSWORD
- PUSHER_HOST=$PUSHER_HOST
- PUSHER_PORT=$PUSHER_PORT
- PUSHER_SCHEME=$PUSHER_SCHEME
- PUSHER_APP_CLUSTER=$PUSHER_APP_CLUSTER
- SOKETI_DB_REDIS_USERNAME=$SOKETI_DB_REDIS_USERNAME
- SOKETI_DB_REDIS_PASSWORD=$SOKETI_DB_REDIS_PASSWORD
- METRICS_HOST=$METRICS_HOST
healthcheck:
test: ["CMD", "php-fpm-healthcheck"]
start_period: 10s

View file

@ -1,5 +1,5 @@
# documentation: https://pterodactyl.io/
# slogan: Wings is Pterodactyl's server control plane
# slogan: The server control plane for Pterodactyl Panel. Written from the ground-up with security, speed, and stability in mind.
# category: devtools
# tags: game, game server, management, panel, minecraft
# logo: svgs/pterodactyl.png

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,8 @@
<?php
use App\Models\Server;
use Illuminate\Support\Once;
/*
|--------------------------------------------------------------------------
| Test Case
@ -12,6 +15,22 @@
*/
uses(Tests\TestCase::class)->in('Feature');
/*
|--------------------------------------------------------------------------
| Test Hooks
|--------------------------------------------------------------------------
|
| Global hooks that run before/after each test.
|
*/
beforeEach(function () {
// Flush the Once memoization cache to ensure tests get fresh data
Once::flush();
// Flush the Server identity map cache to ensure tests get fresh data
Server::flushIdentityMap();
});
/*
|--------------------------------------------------------------------------
| Expectations

View file

@ -1,10 +1,10 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.461"
"version": "4.0.0-beta.462"
},
"nightly": {
"version": "4.0.0-beta.462"
"version": "4.0.0-beta.463"
},
"helper": {
"version": "1.0.12"