* chore(version): update coolify-realtime to version 1.0.9 in docker-compose and versions files * feat(migration): add is_sentinel_enabled column to server_settings with default true * fix(migration): update default value handling for is_sentinel_enabled column in server_settings * feat(seeder): dispatch StartProxy action for each server in ProductionSeeder * feat(seeder): add CheckAndStartSentinelJob dispatch for each server in ProductionSeeder * fix(seeder): conditionally dispatch CheckAndStartSentinelJob based on server's sentinel status * feat(seeder): conditionally dispatch StartProxy action based on proxy check result * refactor(ui): terminal * refactor(ui): remove terminal header from execute-container-command view * refactor(ui): remove unnecessary padding from deployment, backup, and logs sections * fix(service): disable healthcheck logging for Gotenberg (#6005) * fix(service): Joplin volume name (#5930) * chore(version): update coolify version to 4.0.0-beta.420 and nightly version to 4.0.0-beta.421 * fix(server): update sentinelUpdatedAt assignment to use server's sentinel_updated_at property * feat(service): update Changedetection template (#5937) * chore(service): changedetection remove unused code * fix(service): audiobookshelf healthcheck command (#5993) * refactor(service): update Hoarder to their new name karakeep (#5964) * fix(service): downgrade Evolution API phone version (#5977) * feat(service): add Miniflux service (#5843) * refactor(service): karakeep naming and formatting * refactor(service): improve miniflux - improve DB url - add depends_on - formatting, naming & order * feat(service): add Pingvin Share service (#5969) * fix(service): pingvinshare-with-clamav - add platform to make clamav work - formatting * feat(auth): Add Discord OAuth Provider (#5552) * feat(auth): Add Clerk OAuth Provider (#5553) * feat(auth): add Zitadel OAuth Provider (#5490) * Update composer.lock * fix(ssh): scp requires square brackets for ipv6 (#6001) * refactor(core): rename API rate limit ENV * refactor(ui): simplify container selection form in execute-container-command view * chore(service): Update Evolution API image to the official one (#6031) * chore(versions): bump coolify versions to v4.0.0-beta.420 and v4.0.0-beta.421 * fix(github): changing github app breaks the webhook. it does not anymore * feat(service): enhance service status handling and UI updates * fix(parser): improve FQDN generation and update environment variable handling * fix(ui): enhance status refresh buttons with loading indicators * fix(ui): update confirmation button text for stopping database and service * fix(routes): update middleware for deploy route to use 'api.ability:deploy' * fix(ui): refine API token creation form and update helper text for clarity * fix(ui): adjust layout of deployments section for improved alignment * chore(dependencies): update composer dependencies to latest versions including resend-laravel to ^0.19.0 and aws-sdk-php to 3.347.0 * refactor(email): streamline SMTP and resend settings logic for improved clarity * fix(ui): adjust project grid layout and refine server border styling for better visibility * fix(ui): update border styling for consistency across components and enhance loading indicators * feat(cleanup): add functionality to delete teams with no members or servers in CleanupStuckedResources command * refactor(invitation): rename methods for consistency and enhance invitation deletion logic * refactor(user): streamline user deletion process and enhance team management logic * fix(ui): add padding to section headers in settings views for improved spacing * fix(ui): reduce gap between input fields in email settings for better alignment * fix(docker): conditionally enable gzip compression in Traefik labels based on configuration * fix(parser): enable gzip compression conditionally for Pocketbase images and streamline service creation logic * fix(ui): update padding for trademarks policy and enhance spacing in advanced settings section * feat(ui): add heart icon and enhance popup messaging for sponsorship support * feat(settings): add sponsorship popup toggle and corresponding database migration * fix(ui): correct closing tag for sponsorship link in layout popups * fix(ui): refine wording in sponsorship donation prompt in layout popups * fix(ui): update navbar icon color and enhance popup layout for sponsorship support * Update resources/views/livewire/project/shared/health-checks.blade.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update app/Livewire/Subscription/Index.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix(ui): add target="_blank" to sponsorship links in layout popups for improved user experience * fix(models): refine comment wording in User model for clarity on user deletion criteria * Update app/Providers/RouteServiceProvider.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix(models): improve user deletion logic in User model to handle team member roles and prevent deletion if user is alone in root team * fix(ui): update wording in sponsorship prompt for clarity and engagement --------- Co-authored-by: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Co-authored-by: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Co-authored-by: Carsten <BanditsBacon@users.noreply.github.com> Co-authored-by: Alberto Rizzi <48057685+albertorizzi@users.noreply.github.com> Co-authored-by: Jonas Klesen <deklesen@gmail.com> Co-authored-by: Stew Night. <22344601+stewnight@users.noreply.github.com> Co-authored-by: Jeffer Marcelino <jeffersunde72@gmail.com> Co-authored-by: Lucas Eduardo <lucas59356@gmail.com> Co-authored-by: CrazyTim71 <118295691+CrazyTim71@users.noreply.github.com> Co-authored-by: Yassir Elmarissi <yassir.elmarissi@hm.edu> Co-authored-by: Hauke Schnau <hauke@schnau-lilienthal.de> Co-authored-by: Darren Sisson <74752850+djsisson@users.noreply.github.com> Co-authored-by: Alkesh Das <67038642+smad-bro@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
380 lines
22 KiB
PHP
380 lines
22 KiB
PHP
<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 top-0 z-50 py-2">
|
|
<input autocomplete="off" x-ref="searchInput" class="input-sticky"
|
|
:class="{ 'input-sticky-active': isSticky }" x-model="search" placeholder="Type / to search..."
|
|
@keydown.window.slash.prevent="$refs.searchInput.focus()">
|
|
</div>
|
|
<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="application.description"></span>
|
|
</x-slot>
|
|
<x-slot:logo>
|
|
<img class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 dark:opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:bg-white/10 bg-black/10"
|
|
:src="application.logo">
|
|
</x-slot:logo>
|
|
</x-resource-view>
|
|
</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>
|
|
<x-slot:logo> <img
|
|
class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 dark:opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:bg-white/10 bg-black/10 "
|
|
:src="application.logo"></x-slot>
|
|
</x-resource-view>
|
|
</div>
|
|
</template>
|
|
</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>
|
|
<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>
|
|
</div>
|
|
<div x-show="filteredServices.length > 0">
|
|
<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>
|
|
|
|
<div class="grid justify-start grid-cols-1 gap-4 text-left xl:grid-cols-2">
|
|
<template x-for="service in filteredServices" :key="service.name">
|
|
<div 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-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 dark:opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:bg-white/10 bg-black/10"
|
|
: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-resource-view>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div
|
|
x-show="filteredGitBasedApplications.length === 0 && filteredDockerBasedApplications.length === 0 && filteredDatabases.length === 0 && filteredServices.length === 0 && loading === false">
|
|
<div>No resources found.</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
function sortFn(a, b) {
|
|
return a.name.localeCompare(b.name)
|
|
}
|
|
|
|
function searchResources() {
|
|
return {
|
|
search: '',
|
|
loading: false,
|
|
isSticky: false,
|
|
selecting: false,
|
|
services: [],
|
|
gitBasedApplications: [],
|
|
dockerBasedApplications: [],
|
|
databases: [],
|
|
setType(type) {
|
|
if (this.selecting) return;
|
|
this.selecting = true;
|
|
this.$wire.setType(type);
|
|
},
|
|
async loadResources() {
|
|
this.loading = true;
|
|
const {
|
|
services,
|
|
gitBasedApplications,
|
|
dockerBasedApplications,
|
|
databases
|
|
} = await this.$wire.loadServices();
|
|
this.services = services;
|
|
this.gitBasedApplications = gitBasedApplications;
|
|
this.dockerBasedApplications = dockerBasedApplications;
|
|
this.databases = databases;
|
|
this.loading = false;
|
|
this.$nextTick(() => {
|
|
this.$refs.searchInput.focus();
|
|
});
|
|
},
|
|
filterAndSort(items, isSort = true) {
|
|
const searchLower = this.search.trim().toLowerCase();
|
|
|
|
if (searchLower === '') {
|
|
return isSort ? Object.values(items).sort(sortFn) : Object.values(items);
|
|
}
|
|
const filtered = Object.values(items).filter(item => {
|
|
return (item.name?.toLowerCase().includes(searchLower) ||
|
|
item.description?.toLowerCase().includes(searchLower) ||
|
|
item.slogan?.toLowerCase().includes(searchLower))
|
|
})
|
|
return isSort ? filtered.sort(sortFn) : filtered;
|
|
},
|
|
get filteredGitBasedApplications() {
|
|
if (this.gitBasedApplications.length === 0) {
|
|
return [];
|
|
}
|
|
return [
|
|
this.gitBasedApplications,
|
|
].flatMap((items) => this.filterAndSort(items, false));
|
|
},
|
|
get filteredDockerBasedApplications() {
|
|
if (this.dockerBasedApplications.length === 0) {
|
|
return [];
|
|
}
|
|
return [
|
|
this.dockerBasedApplications,
|
|
].flatMap((items) => this.filterAndSort(items, false));
|
|
},
|
|
get filteredDatabases() {
|
|
if (this.databases.length === 0) {
|
|
return [];
|
|
}
|
|
return [
|
|
this.databases,
|
|
].flatMap((items) => this.filterAndSort(items, false));
|
|
},
|
|
get filteredServices() {
|
|
if (this.services.length === 0) {
|
|
return [];
|
|
}
|
|
return [
|
|
this.services,
|
|
].flatMap((items) => this.filterAndSort(items, true));
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
@endif
|
|
</div>
|
|
@if ($current_step === 'servers')
|
|
<h2>Select a server</h2>
|
|
<div class="pb-5"></div>
|
|
<div class="flex flex-col justify-center gap-4 text-left xl:flex-row xl:flex-wrap">
|
|
@if ($onlyBuildServerAvailable)
|
|
<div> Only build servers are available, you need at least one server that is not set as build
|
|
server. <a class="underline dark:text-white" href="/servers">
|
|
Go to servers page
|
|
</a> </div>
|
|
@else
|
|
@forelse($servers as $server)
|
|
<div class="w-full box group" wire:click="setServer({{ $server }})">
|
|
<div class="flex flex-col mx-6">
|
|
<div class="box-title">
|
|
{{ $server->name }}
|
|
</div>
|
|
<div class="box-description">
|
|
{{ $server->description }}</div>
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<div>
|
|
|
|
<div>No validated & reachable servers found. <a class="underline dark:text-white"
|
|
href="/servers">
|
|
Go to servers page
|
|
</a></div>
|
|
</div>
|
|
@endforelse
|
|
@endif
|
|
</div>
|
|
@endif
|
|
@if ($current_step === 'destinations')
|
|
<h2>Select a destination</h2>
|
|
<div>Destinations are used to segregate resources by network. If you are unsure, select the default
|
|
Standalone Docker (coolify).</div>
|
|
<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="flex flex-col mx-6">
|
|
<div class="font-bold dark:group-hover:text-white">
|
|
Swarm Docker <span class="text-xs">({{ $swarmDocker->name }})</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
@else
|
|
@foreach ($standaloneDockers as $standaloneDocker)
|
|
<div class="w-full box 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>
|
|
</div>
|
|
<div class="box-description">
|
|
Network: {{ $standaloneDocker->network }}</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
@endif
|
|
</div>
|
|
@endif
|
|
@if ($current_step === 'select-postgresql-type')
|
|
<div x-data="{ selecting: false }">
|
|
<h2>Select a Postgresql type</h2>
|
|
<div>If you need extra extensions, you can select Supabase PostgreSQL (or others), otherwise select
|
|
PostgreSQL
|
|
17 (default).</div>
|
|
<div class="flex flex-col gap-6 pt-8">
|
|
<div class="gap-2 border border-transparent box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
|
|
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }"
|
|
x-on:click="!selecting && (selecting = true, $wire.setPostgresqlType('postgres:17-alpine'))"
|
|
:disabled="selecting">
|
|
<div class="flex flex-col">
|
|
<div class="box-title">PostgreSQL 17 (default)</div>
|
|
<div class="box-description">
|
|
PostgreSQL is a powerful, open-source object-relational database system (no extensions).
|
|
</div>
|
|
</div>
|
|
<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-6000"
|
|
onclick="event.stopPropagation()" href="https://hub.docker.com/_/postgres/"
|
|
target="_blank">
|
|
Documentation
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="gap-2 border border-transparent box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
|
|
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }"
|
|
x-on:click="!selecting && (selecting = true, $wire.setPostgresqlType('supabase/postgres:17.4.1.032'))"
|
|
:disabled="selecting">
|
|
<div class="flex flex-col">
|
|
<div class="box-title">Supabase PostgreSQL (with extensions)</div>
|
|
<div class="box-description">
|
|
Supabase is a modern, open-source alternative to PostgreSQL with lots of extensions.
|
|
</div>
|
|
</div>
|
|
<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">
|
|
Documentation
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="gap-2 border border-transparent box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
|
|
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }"
|
|
x-on:click="!selecting && (selecting = true, $wire.setPostgresqlType('postgis/postgis:17-3.5-alpine'))"
|
|
:disabled="selecting">
|
|
<div class="flex flex-col">
|
|
<div class="box-title">PostGIS (AMD only)</div>
|
|
<div class="box-description">
|
|
PostGIS is a PostgreSQL extension for geographic objects.
|
|
</div>
|
|
</div>
|
|
<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/postgis/docker-postgis"
|
|
target="_blank">
|
|
Documentation
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="gap-2 border border-transparent box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
|
|
:class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }"
|
|
x-on:click="!selecting && (selecting = true, $wire.setPostgresqlType('pgvector/pgvector:pg17'))"
|
|
:disabled="selecting">
|
|
<div class="flex flex-col">
|
|
<div class="box-title">PGVector (17)</div>
|
|
<div class="box-description">
|
|
PGVector is a PostgreSQL extension for vector data types.
|
|
</div>
|
|
</div>
|
|
<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/pgvector/pgvector"
|
|
target="_blank">
|
|
Documentation
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
@if ($current_step === 'existing-postgresql')
|
|
<form wire:submit='addExistingPostgresql' class="flex items-end gap-4">
|
|
<x-forms.input placeholder="postgres://username:password@database:5432" label="Database URL"
|
|
id="existingPostgresqlUrl" />
|
|
<x-forms.button type="submit">Add Database</x-forms.button>
|
|
</form>
|
|
@endif
|
|
</div>
|