feat(ui): Improve new resource page UI layout and styling (#7291)

This commit is contained in:
Andras Bacsai
2025-11-28 14:01:17 +01:00
committed by GitHub
24 changed files with 257 additions and 159 deletions
@@ -13,7 +13,7 @@
<div class="subtitle">All your projects are here.</div>
<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 box group">
<div class="relative gap-2 cursor-pointer coolbox group">
<a href="{{ $project->navigateTo() }}" class="absolute inset-0"></a>
<div class="flex flex-1 mx-6">
<div class="flex flex-col justify-center flex-1">
@@ -7,7 +7,7 @@
<div class="flex flex-col justify-center gap-2 text-left ">
@forelse ($private_keys as $key)
@if ($private_key_id == $key->id)
<div class="gap-2 py-4 cursor-pointer group hover:bg-coollabs bg-coolgray-100 box"
<div class="gap-2 py-4 cursor-pointer group hover:bg-coollabs bg-coolgray-100 coolbox"
wire:click="setPrivateKey('{{ $key->id }}')" wire:key="{{ $key->id }}">
<div class="flex flex-col mx-6">
<div class="box-title">
@@ -20,7 +20,7 @@
</div>
</div>
@else
<div class="gap-2 py-4 cursor-pointer group hover:bg-coollabs bg-coolgray-100 box"
<div class="gap-2 py-4 cursor-pointer group hover:bg-coollabs bg-coolgray-100 coolbox"
wire:click="setPrivateKey('{{ $key->id }}')" wire:key="{{ $key->id }}">
<div class="flex flex-col mx-6">
<div class="box-title">
@@ -21,7 +21,7 @@
<div class="flex flex-col justify-center gap-2 text-left">
@foreach ($github_apps as $ghapp)
<div class="flex">
<div class="w-full gap-2 py-4 bg-white cursor-pointer group hover:bg-coollabs dark:bg-coolgray-200 box"
<div class="w-full gap-2 py-4 bg-white cursor-pointer group hover:bg-coollabs dark:bg-coolgray-200 coolbox"
wire:click.prevent="loadRepositories({{ $ghapp->id }})"
wire:key="{{ $ghapp->id }}">
<div class="flex mr-4">
@@ -1,18 +1,19 @@
<div x-data x-init="$wire.loadServers">
<div class="flex flex-col gap-4 lg:flex-row ">
<h1>New Resource</h1>
<div class="w-full pb-4 lg:w-96 lg:pb-0">
<x-forms.select wire:model.live="selectedEnvironment">
@foreach ($environments as $environment)
<option value="{{ $environment->name }}">Environment: {{ $environment->name }}</option>
@endforeach
</x-forms.select>
</div>
</div>
<div class="pb-4">Deploy resources, like Applications, Databases, Services...</div>
<div x-data="searchResources()">
@if ($current_step === 'type')
<div x-init="window.addEventListener('scroll', () => isSticky = window.pageYOffset > 100)" class="sticky z-10 top-10 py-2 bg-white/95 dark:bg-base/95 backdrop-blur-sm">
<div x-init="window.addEventListener('scroll', () => isSticky = window.pageYOffset > 100)"
class="sticky z-10 top-0 backdrop-blur-sm border-b border-neutral-200 dark:border-coolgray-400">
<div class="flex flex-col gap-4 lg:flex-row">
<h1>New Resource</h1>
<div class="w-full lg:w-96">
<x-forms.select wire:model.live="selectedEnvironment">
@foreach ($environments as $environment)
<option value="{{ $environment->name }}">Environment: {{ $environment->name }}</option>
@endforeach
</x-forms.select>
</div>
</div>
<div class="mb-4">Deploy resources, like Applications, Databases, Services...</div>
<div class="flex gap-2 items-start">
<input autocomplete="off" x-ref="searchInput" class="input-sticky flex-1"
:class="{ 'input-sticky-active': isSticky }" x-model="search" placeholder="Type / to search..."
@@ -23,25 +24,34 @@
<div x-show="loading || categories.length === 0"
class="flex items-center justify-between gap-2 py-1.5 px-3 w-64 text-sm rounded-sm border-0 ring-2 ring-inset ring-neutral-200 dark:ring-coolgray-300 bg-neutral-100 dark:bg-coolgray-200 cursor-not-allowed whitespace-nowrap opacity-50">
<span class="text-sm text-neutral-400 dark:text-neutral-600">Filter by category</span>
<svg class="w-4 h-4 text-neutral-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
<svg class="w-4 h-4 text-neutral-400 shrink-0" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7" />
</svg>
</div>
<!-- Active State -->
<div x-show="!loading && categories.length > 0"
@click="openCategoryDropdown = !openCategoryDropdown; $nextTick(() => { if (openCategoryDropdown) $refs.categorySearchInput.focus() })"
class="flex items-center justify-between gap-2 py-1.5 px-3 w-64 text-sm rounded-sm border-0 ring-2 ring-inset ring-neutral-200 dark:ring-coolgray-300 bg-white dark:bg-coolgray-100 cursor-pointer hover:ring-coolgray-400 transition-all whitespace-nowrap">
<span class="text-sm truncate" x-text="selectedCategory === '' ? 'Filter by category' : selectedCategory" :class="selectedCategory === '' ? 'text-neutral-400 dark:text-neutral-600' : 'capitalize text-black dark:text-white'"></span>
<svg class="w-4 h-4 transition-transform text-neutral-400 shrink-0" :class="{ 'rotate-180': openCategoryDropdown }" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
<span class="text-sm truncate"
x-text="selectedCategory === '' ? 'Filter by category' : selectedCategory"
:class="selectedCategory === '' ? 'text-neutral-400 dark:text-neutral-600' :
'capitalize text-black dark:text-white'"></span>
<svg class="w-4 h-4 transition-transform text-neutral-400 shrink-0"
:class="{ 'rotate-180': openCategoryDropdown }" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7" />
</svg>
</div>
<!-- Dropdown Menu -->
<div x-show="openCategoryDropdown" x-transition
class="absolute z-50 w-full mt-1 bg-white dark:bg-coolgray-100 border border-neutral-300 dark:border-coolgray-400 rounded shadow-lg overflow-hidden">
<div class="sticky top-0 p-2 bg-white dark:bg-coolgray-100 border-b border-neutral-300 dark:border-coolgray-400">
<input type="text" x-ref="categorySearchInput" x-model="categorySearch" placeholder="Search categories..."
<div
class="sticky top-0 p-2 bg-white dark:bg-coolgray-100 border-b border-neutral-300 dark:border-coolgray-400">
<input type="text" x-ref="categorySearchInput" x-model="categorySearch"
placeholder="Search categories..."
class="w-full px-2 py-1 text-sm rounded border border-neutral-300 dark:border-coolgray-400 bg-white dark:bg-coolgray-200 focus:outline-none focus:ring-2 focus:ring-coolgray-400"
@click.stop>
</div>
@@ -51,7 +61,9 @@
:class="{ 'bg-neutral-50 dark:bg-coolgray-300': selectedCategory === '' }">
<span class="text-sm">All Categories</span>
</div>
<template x-for="category in categories.filter(cat => categorySearch === '' || cat.toLowerCase().includes(categorySearch.toLowerCase()))" :key="category">
<template
x-for="category in categories.filter(cat => categorySearch === '' || cat.toLowerCase().includes(categorySearch.toLowerCase()))"
:key="category">
<div @click="selectedCategory = category; categorySearch = ''; openCategoryDropdown = false"
class="px-3 py-2 cursor-pointer hover:bg-neutral-100 dark:hover:bg-coolgray-200 capitalize"
:class="{ 'bg-neutral-50 dark:bg-coolgray-300': selectedCategory === category }">
@@ -66,109 +78,114 @@
<div x-show="loading">Loading...</div>
<div x-show="!loading" class="flex flex-col gap-4 py-4">
<h2 x-show="filteredGitBasedApplications.length > 0">Applications</h2>
<h4 x-show="filteredGitBasedApplications.length > 0">Git Based</h4>
<div x-show="filteredGitBasedApplications.length > 0"
class="grid justify-start grid-cols-1 gap-4 text-left xl:grid-cols-1">
<template x-for="application in filteredGitBasedApplications" :key="application.name">
<div x-on:click='setType(application.id)'
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }">
<x-resource-view>
<x-slot:title><span x-text="application.name"></span></x-slot>
<x-slot:description>
<span x-html="window.sanitizeHTML(application.description)"></span>
<div x-show="filteredGitBasedApplications.length > 0 || filteredDockerBasedApplications.length > 0"
class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div x-show="filteredGitBasedApplications.length > 0" class="space-y-4">
<h4>Git Based</h4>
<div class="grid justify-start grid-cols-1 gap-4 text-left">
<template x-for="application in filteredGitBasedApplications" :key="application.name">
<div x-on:click='setType(application.id)'
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }">
<x-resource-view>
<x-slot:title><span x-text="application.name"></span></x-slot>
<x-slot:description>
<span x-html="window.sanitizeHTML(application.description)"></span>
</x-slot>
<x-slot:logo>
<img class="w-full h-full p-2 transition-all duration-200 dark:bg-white/10 bg-black/10 object-contain"
:src="application.logo">
</x-slot:logo>
</x-resource-view>
</x-resource-view>
</div>
</template>
</div>
</template>
</div>
<h4 x-show="filteredDockerBasedApplications.length > 0">Docker Based</h4>
<div x-show="filteredDockerBasedApplications.length > 0"
class="grid justify-start grid-cols-1 gap-4 text-left xl:grid-cols-3">
<template x-for="application in filteredDockerBasedApplications" :key="application.name">
<div x-on:click="setType(application.id)"
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }">
<x-resource-view>
<x-slot:title><span x-text="application.name"></span></x-slot>
<x-slot:description><span x-text="application.description"></span></x-slot>
</div>
<div x-show="filteredDockerBasedApplications.length > 0" class="space-y-4">
<h4>Docker Based</h4>
<div class="grid justify-start grid-cols-1 gap-4 text-left">
<template x-for="application in filteredDockerBasedApplications" :key="application.name">
<div x-on:click="setType(application.id)"
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }">
<x-resource-view>
<x-slot:title><span x-text="application.name"></span></x-slot>
<x-slot:description><span x-text="application.description"></span></x-slot>
<x-slot:logo> <img
class="w-full h-full p-2 transition-all duration-200 dark:bg-white/10 bg-black/10 object-contain"
:src="application.logo"></x-slot>
</x-resource-view>
</x-resource-view>
</div>
</template>
</div>
</template>
</div>
</div>
<h2 x-show="filteredDatabases.length > 0">Databases</h2>
<div x-show="filteredDatabases.length > 0"
class="grid justify-start grid-cols-1 gap-4 text-left xl:grid-cols-2">
<template x-for="database in filteredDatabases" :key="database.id">
<div x-on:click="setType(database.id)"
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }">
<x-resource-view>
<x-slot:title><span x-text="database.name"></span></x-slot>
<div x-show="filteredDatabases.length > 0" class="mt-8">
<h2 class="mb-4">Databases</h2>
<div class="grid justify-start grid-cols-1 gap-4 text-left xl:grid-cols-3">
<template x-for="database in filteredDatabases" :key="database.id">
<div x-on:click="setType(database.id)"
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }">
<x-resource-view>
<x-slot:title><span x-text="database.name"></span></x-slot>
<x-slot:description><span x-text="database.description"></span></x-slot>
<x-slot:logo>
<span x-show="database.logo">
<span x-html="database.logo"></span>
</span>
</x-slot>
</x-resource-view>
</div>
</template>
<x-slot:logo>
<span x-show="database.logo">
<span x-html="database.logo"></span>
</span>
</x-slot>
</x-resource-view>
</div>
</template>
</div>
</div>
<div x-show="filteredServices.length > 0">
<div x-show="filteredServices.length > 0" class="mt-8">
<div class="flex items-center gap-4" x-init="loadResources">
<h2>Services</h2>
<x-forms.button x-on:click="loadResources">Reload List</x-forms.button>
</div>
<div class="py-4 text-xs">Trademarks Policy: The respective trademarks mentioned here are owned by
the
respective
companies, and use of them does not imply any affiliation or endorsement.<br>Find more services
<a class="dark:text-white underline" target="_blank"
href="https://coolify.io/docs/services/overview">here</a>.
</div>
<x-callout type="info" title="Trademarks Policy" class="mt-4 mb-6">
The respective trademarks mentioned here are owned by the respective companies, and use of them
does not imply any affiliation or endorsement.
</x-callout>
<div class="grid justify-start grid-cols-1 gap-4 text-left xl:grid-cols-2">
<div class="grid justify-start grid-cols-1 gap-4 text-left xl:grid-cols-3">
<template x-for="service in filteredServices" :key="service.name">
<div x-on:click="setType('one-click-service-' + service.name)"
<div class="relative" x-on:click="setType('one-click-service-' + service.name)"
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }">
<x-resource-view>
<x-slot:title>
<template x-if="service.name">
<span x-text="service.name"></span>
</template>
</x-slot>
<x-slot:description>
<template x-if="service.slogan">
<span x-text="service.slogan"></span>
</template>
</x-slot>
<x-slot:logo>
<template x-if="service.logo">
<img class="w-full h-full p-2 transition-all duration-200 dark:bg-white/10 bg-black/10 object-contain"
:src='service.logo'
x-on:error.window="$event.target.src = service.logo_github_url"
onerror="this.onerror=null; this.src=this.getAttribute('data-fallback');"
x-on:error="$event.target.src = '/coolify-logo.svg'"
:data-fallback='service.logo_github_url' />
</template>
</x-slot:logo>
<x-slot:documentation>
<template x-if="service.documentation">
<div class="flex items-center px-2" title="Read the documentation.">
<a class="p-2 rounded-sm hover:bg-gray-100 dark:hover:bg-coolgray-200 hover:no-underline dark:group-hover:text-white text-neutral-600"
onclick="event.stopPropagation()" :href="service.documentation"
target="_blank">
Docs
</a>
</div>
</template>
</x-slot:documentation>
</x-slot>
<x-slot:description>
<template x-if="service.slogan">
<span x-text="service.slogan"></span>
</template>
</x-slot>
<x-slot:logo>
<template x-if="service.logo">
<img class="w-full h-full p-2 transition-all duration-200 dark:bg-white/10 bg-black/10 object-contain"
:src='service.logo'
x-on:error.window="$event.target.src = service.logo_github_url"
onerror="this.onerror=null; this.src=this.getAttribute('data-fallback');"
x-on:error="$event.target.src = '/coolify-logo.svg'"
:data-fallback='service.logo_github_url' />
</template>
</x-slot:logo>
</x-resource-view>
<template x-if="shouldShowDocIcon(service)">
<a :href="getDocLink(service) || coolifyDocsUrl(service.name)" target="_blank"
@click.stop @mouseenter="resolveDocLink(service)"
class="absolute top-2 right-2 p-1.5 rounded hover:bg-neutral-200 dark:hover:bg-coolgray-300 transition-colors"
:class="{ 'opacity-50': docCheckInProgress[service.name] }"
title="View documentation">
<svg class="w-4 h-4 text-neutral-600 dark:text-neutral-400" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</a>
</template>
</div>
</template>
</div>
@@ -195,6 +212,8 @@
gitBasedApplications: [],
dockerBasedApplications: [],
databases: [],
docLinkCache: {}, // Cache resolved doc URLs: { serviceName: url | null }
docCheckInProgress: {}, // Track ongoing checks: { serviceName: boolean }
setType(type) {
if (this.selecting) return;
this.selecting = true;
@@ -219,6 +238,81 @@
this.$refs.searchInput.focus();
});
},
extractBaseServiceName(serviceName) {
// Convert to lowercase and replace spaces with dashes to match original format
const normalized = serviceName.toLowerCase().replace(/\s+/g, '-');
// Remove flavor suffixes: -with-*, -without-*
return normalized.replace(/-(with|without)-.+$/, '');
},
coolifyDocsUrl(serviceName) {
const baseName = this.extractBaseServiceName(serviceName);
return 'https://coolify.io/docs/services/' + baseName;
},
officialDocsUrl(service) {
return service.documentation || null;
},
async checkUrlExists(url) {
if (!url) return false;
try {
const response = await fetch(url, {
method: 'HEAD'
});
return response.ok;
} catch (e) {
// CORS error or network error - assume URL exists
return true;
}
},
async resolveDocLink(service) {
const serviceName = service.name;
// Already cached?
if (this.docLinkCache.hasOwnProperty(serviceName)) {
return this.docLinkCache[serviceName];
}
// Already checking?
if (this.docCheckInProgress[serviceName]) {
return null;
}
this.docCheckInProgress[serviceName] = true;
// 1. Try Coolify docs first
const coolifyUrl = this.coolifyDocsUrl(serviceName);
const coolifyExists = await this.checkUrlExists(coolifyUrl);
if (coolifyExists) {
this.docLinkCache[serviceName] = coolifyUrl;
this.docCheckInProgress[serviceName] = false;
return coolifyUrl;
}
// 2. Fall back to official docs
const officialUrl = this.officialDocsUrl(service);
if (officialUrl) {
const officialExists = await this.checkUrlExists(officialUrl);
if (officialExists) {
this.docLinkCache[serviceName] = officialUrl;
this.docCheckInProgress[serviceName] = false;
return officialUrl;
}
}
// 3. Both failed - cache null to hide icon
this.docLinkCache[serviceName] = null;
this.docCheckInProgress[serviceName] = false;
return null;
},
getDocLink(service) {
return this.docLinkCache[service.name];
},
shouldShowDocIcon(service) {
const cached = this.docLinkCache[service.name];
// Show icon if: not checked yet OR has a valid URL
return cached === undefined || cached !== null;
},
filterAndSort(items, isSort = true) {
const searchLower = this.search.trim().toLowerCase();
let filtered = Object.values(items);
@@ -229,9 +323,10 @@
filtered = filtered.filter(item => {
if (!item.category) return false;
// Handle comma-separated categories
const categories = item.category.includes(',')
? item.category.split(',').map(c => c.trim().toLowerCase())
: [item.category.toLowerCase()];
const categories = item.category.includes(',') ?
item.category.split(',').map(c => c.trim().toLowerCase()) : [item.category
.toLowerCase()
];
return categories.includes(selectedCategoryLower);
});
}
@@ -295,7 +390,7 @@
</a> </div>
@else
@forelse($servers as $server)
<div class="w-full box group" wire:click="setServer({{ $server }})">
<div class="w-full coolbox group" wire:click="setServer({{ $server }})">
<div class="flex flex-col mx-6">
<div class="box-title">
{{ $server->name }}
@@ -308,7 +403,8 @@
@empty
<div>
<div>No validated & reachable servers found. <a class="underline dark:text-white" href="/servers">
<div>No validated & reachable servers found. <a class="underline dark:text-white"
href="/servers">
Go to servers page
</a></div>
</div>
@@ -324,7 +420,7 @@
<div class="flex flex-col justify-center gap-4 text-left xl:flex-row xl:flex-wrap">
@if ($server->isSwarm())
@foreach ($swarmDockers as $swarmDocker)
<div class="w-full box group" wire:click="setDestination('{{ $swarmDocker->uuid }}')">
<div class="w-full coolbox group" wire:click="setDestination('{{ $swarmDocker->uuid }}')">
<div class="flex flex-col mx-6">
<div class="font-bold dark:group-hover:text-white">
Swarm Docker <span class="text-xs">({{ $swarmDocker->name }})</span>
@@ -334,7 +430,7 @@
@endforeach
@else
@foreach ($standaloneDockers as $standaloneDocker)
<div class="w-full box group" wire:click="setDestination('{{ $standaloneDocker->uuid }}')">
<div class="w-full coolbox group" wire:click="setDestination('{{ $standaloneDocker->uuid }}')">
<div class="flex flex-col mx-6">
<div class="box-title">
Standalone Docker <span class="text-xs">({{ $standaloneDocker->name }})</span>
@@ -368,7 +464,8 @@
<div class="flex items-center px-2" title="Read the documentation.">
<a class="p-2 hover:underline dark:group-hover:text-white dark:text-white text-neutral-6000"
onclick="event.stopPropagation()" href="https://hub.docker.com/_/postgres/" target="_blank">
onclick="event.stopPropagation()" href="https://hub.docker.com/_/postgres/"
target="_blank">
Documentation
</a>
</div>
@@ -386,7 +483,8 @@
<div class="flex-1"></div>
<div class="flex items-center px-2" title="Read the documentation.">
<a class="p-2 hover:underline dark:group-hover:text-white dark:text-white text-neutral-600"
onclick="event.stopPropagation()" href="https://github.com/supabase/postgres" target="_blank">
onclick="event.stopPropagation()" href="https://github.com/supabase/postgres"
target="_blank">
Documentation
</a>
</div>
@@ -424,7 +522,8 @@
<div class="flex items-center px-2" title="Read the documentation.">
<a class="p-2 hover:underline dark:group-hover:text-white dark:text-white text-neutral-600"
onclick="event.stopPropagation()" href="https://github.com/pgvector/pgvector" target="_blank">
onclick="event.stopPropagation()" href="https://github.com/pgvector/pgvector"
target="_blank">
Documentation
</a>
</div>
@@ -439,4 +538,4 @@
<x-forms.button type="submit">Add Database</x-forms.button>
</form>
@endif
</div>
</div>
@@ -54,7 +54,7 @@
@if ($environment->isEmpty())
@can('createAnyResource')
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"
class="items-center justify-center box">+ Add Resource</a>
class="items-center justify-center coolbox">+ Add Resource</a>
@else
<div
class="flex flex-col items-center justify-center p-8 text-center border border-dashed border-neutral-300 dark:border-coolgray-300 rounded-lg">
@@ -94,7 +94,7 @@
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
<template x-for="item in filteredApplications" :key="item.uuid">
<span>
<a class="h-24 box group" :href="item.hrefLink">
<a class="h-24 coolbox group" :href="item.hrefLink">
<div class="flex flex-col w-full">
<div class="flex gap-2 px-4">
<div class="pb-2 truncate box-title" x-text="item.name"></div>
@@ -143,7 +143,7 @@
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
<template x-for="item in filteredDatabases" :key="item.uuid">
<span>
<a class="h-24 box group" :href="item.hrefLink">
<a class="h-24 coolbox group" :href="item.hrefLink">
<div class="flex flex-col w-full">
<div class="flex gap-2 px-4">
<div class="pb-2 truncate box-title" x-text="item.name"></div>
@@ -192,7 +192,7 @@
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
<template x-for="item in filteredServices" :key="item.uuid">
<span>
<a class="h-24 box group" :href="item.hrefLink">
<a class="h-24 coolbox group" :href="item.hrefLink">
<div class="flex flex-col w-full">
<div class="flex gap-2 px-4">
<div class="pb-2 truncate box-title" x-text="item.name"></div>
@@ -90,7 +90,7 @@
<div class="grid grid-cols-1 gap-4">
@foreach ($networks as $network)
<div wire:click="addServer('{{ $network->id }}','{{ data_get($network, 'server.id') }}')"
class="relative flex flex-col dark:text-white box group">
class="relative flex flex-col dark:text-white coolbox group">
<div>
<div class="box-title">
Server: {{ data_get($network, 'server.name') }}
@@ -14,7 +14,7 @@
<div class="flex flex-col flex-wrap gap-2 pt-4">
@forelse($resource->scheduled_tasks as $task)
@if ($resource->type() == 'application')
<a class="box"
<a class="coolbox"
href="{{ route('project.application.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}">
<span class="flex flex-col">
<span class="text-lg font-bold">{{ $task->name }}
@@ -29,7 +29,7 @@
</span>
</a>
@elseif ($resource->type() == 'service')
<a class="box"
<a class="coolbox"
href="{{ route('project.service.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}">
<span class="flex flex-col">
<span class="text-lg font-bold">{{ $task->name }}
@@ -21,7 +21,7 @@
<div class="text-xs truncate subtitle lg:text-sm">{{ $project->name }}.</div>
<div class="grid gap-2 lg:grid-cols-2">
@forelse ($project->environments->sortBy('created_at') as $environment)
<div class="gap-2 box group">
<div class="gap-2 coolbox group">
<div class="flex flex-1 mx-6">
<a class="flex flex-col justify-center flex-1"
href="{{ route('project.resource.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid]) }}">