Merge branch 'feature' into feature/oauth

This commit is contained in:
Andras Bacsai
2024-03-20 13:58:31 +01:00
committed by GitHub
155 changed files with 2455 additions and 898 deletions

View File

@@ -419,7 +419,7 @@ const magicActions = [{
},
{
id: 24,
name: 'Goto: Boarding process',
name: 'Goto: Onboarding process',
icon: 'goto',
sequence: ['main', 'redirect']
},
@@ -667,7 +667,7 @@ async function redirect() {
targetUrl.pathname = `/team`
break;
case 24:
targetUrl.pathname = `/boarding`
targetUrl.pathname = `/onboarding`
break;
case 25:
targetUrl.pathname = `/security/api-tokens`

View File

@@ -1,4 +1,7 @@
<div class="w-full">
<div @class([
'flex-1' => $isMultiline,
'w-full' => !$isMultiline,
])>
@if ($label)
<label for="small-input" class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }}
@if ($required)
@@ -10,7 +13,7 @@
</label>
@endif
@if ($type === 'password')
<div class="relative" x-data>
<div class="relative" x-data="{ type: 'password' }">
@if ($allowToPeak)
<div x-on:click="changePasswordFieldType"
class="absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer hover:text-white">
@@ -22,8 +25,9 @@
</svg>
</div>
@endif
<input value="{{ $value }}" {{ $attributes->merge(['class' => $defaultClass]) }}
@required($required) @if ($id !== 'null') wire:model={{ $id }} @endif
<input x-cloak x-show="type" value="{{ $value }}"
{{ $attributes->merge(['class' => $defaultClass]) }} @required($required)
@if ($id !== 'null') wire:model={{ $id }} @endif
wire:dirty.class.remove='text-white' wire:dirty.class="input-warning" wire:loading.attr="disabled"
type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}"
name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
@@ -33,9 +37,11 @@
@else
<input @if ($value) value="{{ $value }}" @endif
{{ $attributes->merge(['class' => $defaultClass]) }} @required($required) @readonly($readonly)
@if ($id !== 'null') wire:model={{ $id }} @endif wire:dirty.class.remove='text-white' wire:dirty.class="input-warning"
wire:loading.attr="disabled" type="{{ $type }}" @disabled($disabled)
@if ($id !== 'null') id={{ $id }} @endif name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}">
@if ($id !== 'null') wire:model={{ $id }} @endif
wire:dirty.class.remove='text-white' wire:dirty.class="input-warning" wire:loading.attr="disabled"
type="{{ $type }}" @disabled($disabled)
@if ($id !== 'null') id={{ $id }} @endif name="{{ $name }}"
placeholder="{{ $attributes->get('placeholder') }}">
@endif
@if (!$label && $helper)
<x-helper :helper="$helper" />

View File

@@ -1,4 +1,4 @@
<div class="form-control">
<div class="flex-1 form-control">
@if ($label)
<label for="small-input" class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }}
@if ($required)
@@ -9,13 +9,46 @@
@endif
</label>
@endif
<textarea placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
@if ($type === 'password')
<div class="relative" x-data="{ type: 'password' }">
@if ($allowToPeak)
<div x-on:click="changePasswordFieldType"
class="absolute inset-y-0 right-0 flex items-center h-6 pt-2 pr-2 cursor-pointer hover:text-white">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" 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="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />
</svg>
</div>
@endif
<input x-cloak x-show="type === 'password'" value="{{ $value }}"
{{ $attributes->merge(['class' => $defaultClassInput]) }} @required($required)
@if ($id !== 'null') wire:model={{ $id }} @endif
wire:dirty.class.remove='text-white' wire:dirty.class="input-warning" wire:loading.attr="disabled"
type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}"
name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
aria-placeholder="{{ $attributes->get('placeholder') }}">
<textarea x-cloak x-show="type !== 'password'" placeholder="{{ $placeholder }}"
{{ $attributes->merge(['class' => $defaultClass]) }}
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
@else
wire:model={{ $value ?? $id }}
wire:dirty.class="input-warning" @endif
@disabled($disabled) @readonly($readonly) @required($required) id="{{ $id }}"
name="{{ $name }}" name={{ $id }}></textarea>
</div>
@else
<textarea placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
@else
wire:model={{ $value ?? $id }}
wire:dirty.class="input-warning" @endif
@disabled($disabled) @readonly($readonly) @required($required) id="{{ $id }}" name="{{ $name }}"
name={{ $id }}></textarea>
wire:model={{ $value ?? $id }}
wire:dirty.class="input-warning" @endif
@disabled($disabled) @readonly($readonly) @required($required) id="{{ $id }}"
name="{{ $name }}" name={{ $id }}></textarea>
@endif
@error($id)
<label class="label">
<span class="text-red-500 label-text-alt">{{ $message }}</span>

View File

@@ -150,7 +150,17 @@
</a>
</li>
@endif
<li title="Notifications" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('notification.index') }}">
<svg class="{{ request()->is('notifications*') ? 'text-warning icon' : '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
</a>
</li>
@if (isInstanceAdmin())
<li title="Settings" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="/settings">
@@ -167,14 +177,14 @@
</a>
</li>
@endif
<li title="Boarding" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('boarding') }}">
<svg class="{{ request()->is('boarding*') ? 'text-warning icon' : 'icon' }}"
<li title="Onboarding" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline" href="{{ route('onboarding') }}">
<svg class="{{ request()->is('onboarding*') ? 'text-warning icon' : '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>
Boarding
Onboarding
</a>
</li>
</div>

View File

@@ -0,0 +1,25 @@
<div class="pb-6">
<div class="flex items-end gap-2">
<h1>Team Notifications</h1>
</div>
<nav class="flex pt-2 pb-10">
<ol class="inline-flex items-center">
<li>
<div class="flex items-center">
<span>Currently active team: <span
class="text-warning">{{ session('currentTeam.name') }}</span></span>
</div>
</li>
</ol>
</nav>
<nav class="navbar-main">
<a class="{{ request()->routeIs('notification.index') ? 'text-white' : '' }}"
href="{{ route('notification.index') }}">
<button>General</button>
</a>
<div class="flex-1"></div>
<div class="-mt-9">
<livewire:switch-team />
</div>
</nav>
</div>

View File

@@ -4,11 +4,13 @@
href="{{ route('server.proxy', $parameters) }}">
<button>Configuration</button>
</a>
@if (data_get($server, 'proxy.type') !== 'NONE')
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'text-white' : '' }}"
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
<button>Dynamic Configurations</button>
</a>
@if ($server->proxyType() !== 'NONE')
{{-- @if ($server->proxyType() === 'TRAEFIK_V2') --}}
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'text-white' : '' }}"
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
<button>Dynamic Configurations</button>
</a>
{{-- @endif --}}
<a class="{{ request()->routeIs('server.proxy.logs') ? 'text-white' : '' }}"
href="{{ route('server.proxy.logs', $parameters) }}">
<button>Logs</button>

View File

@@ -8,6 +8,6 @@
{{ str($status)->before(':')->headline() }}
</div>
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
<div class="text-xs text-success">({{ str($status)->after(':') }})</div>
<div class="text-xs {{ str($status)->contains('unhealthy') ? 'text-warning' : 'text-success' }}">({{ str($status)->after(':') }})</div>
@endif
</div>

View File

@@ -1,7 +1,7 @@
<div class="pb-6">
<div class="flex items-end gap-2">
<h1>Team</h1>
<a href="/team/new"><x-forms.button>+ Add Team</x-forms.button></a>
<a href="/team/new"><x-forms.button>+ Add Team</x-forms.button></a>
</div>
<nav class="flex pt-2 pb-10">
<ol class="inline-flex items-center">
@@ -17,18 +17,15 @@
<a class="{{ request()->routeIs('team.index') ? 'text-white' : '' }}" href="{{ route('team.index') }}">
<button>General</button>
</a>
<a class="{{ request()->routeIs('team.member.index') ? 'text-white' : '' }}" href="{{ route('team.member.index') }}">
<a class="{{ request()->routeIs('team.member.index') ? 'text-white' : '' }}"
href="{{ route('team.member.index') }}">
<button>Members</button>
</a>
<a class="{{ request()->routeIs('team.storage.index') ? 'text-white' : '' }}"
href="{{ route('team.storage.index') }}">
<button>S3 Storages</button>
</a>
<a class="{{ request()->routeIs('team.notification.index') ? 'text-white' : '' }}"
href="{{ route('team.notification.index') }}">
<button>Notifications</button>
</a>
<a class="{{ request()->routeIs('team.shared-variables.index') ? 'text-white' : '' }}"
<a class="{{ request()->routeIs('team.shared-variables.index') ? 'text-white' : '' }}"
href="{{ route('team.shared-variables.index') }}">
<button>Shared Variables</button>
</a>

View File

@@ -27,6 +27,7 @@
<ul x-data="{
toasts: [],
toastsHovered: false,
timeout: null,
expanded: false,
layout: 'default',
position: 'top-center',
@@ -286,14 +287,12 @@
}
stackToasts();
$watch('toastsHovered', function(value) {
if (layout == 'default') {
if (position.includes('bottom')) {
resetBottom();
} else {
resetTop();
}
if (value) {
// calculate the new positions
expanded = true;
@@ -319,13 +318,32 @@
<template x-for="(toast, index) in toasts" :key="toast.id">
<li :id="toast.id" x-data="{
toastHovered: false
toastHovered: false,
}" x-init="if (position.includes('bottom')) {
$el.firstElementChild.classList.add('toast-bottom');
$el.firstElementChild.classList.add('opacity-0', 'translate-y-full');
} else {
$el.firstElementChild.classList.add('opacity-0', '-translate-y-full');
}
$watch('toastsHovered', function(value) {
if (value && this.timeout) {
clearTimeout(this.timeout);
} else {
this.timeout = setTimeout(function() {
setTimeout(function() {
$el.firstElementChild.classList.remove('opacity-100');
$el.firstElementChild.classList.add('opacity-0');
if (toasts.length == 1) {
$el.firstElementChild.classList.remove('translate-y-0');
$el.firstElementChild.classList.add('-translate-y-full');
}
setTimeout(function() {
deleteToastWithId(toast.id)
}, 300);
}, 5);
}, 2000)
}
});
setTimeout(function() {
setTimeout(function() {
@@ -342,7 +360,7 @@
}, 5);
}, 50);
setTimeout(function() {
this.timeout = setTimeout(function() {
setTimeout(function() {
$el.firstElementChild.classList.remove('opacity-100');
$el.firstElementChild.classList.add('opacity-0');
@@ -390,12 +408,12 @@
d="M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM11.9996 7C12.5519 7 12.9996 7.44772 12.9996 8V12C12.9996 12.5523 12.5519 13 11.9996 13C11.4474 13 10.9996 12.5523 10.9996 12V8C10.9996 7.44772 11.4474 7 11.9996 7ZM12.001 14.99C11.4488 14.9892 11.0004 15.4363 10.9997 15.9886L10.9996 15.9986C10.9989 16.5509 11.446 16.9992 11.9982 17C12.5505 17.0008 12.9989 16.5537 12.9996 16.0014L12.9996 15.9914C13.0004 15.4391 12.5533 14.9908 12.001 14.99Z"
fill="currentColor"></path>
</svg>
<p class="leading-2 text-neutral-200"
x-html="toast.message">
<p class="leading-2 text-neutral-200" x-html="toast.message">
</p>
</div>
<p x-show="toast.description" :class="{ 'pl-5': toast.type!='default' }"
class="mt-1.5 text-xs leading-2 opacity-90 whitespace-pre-wrap" x-html="toast.description"></p>
class="mt-1.5 text-xs leading-2 opacity-90 whitespace-pre-wrap"
x-html="toast.description"></p>
</div>
</template>
<template x-if="toast.html">

View File

@@ -76,11 +76,13 @@
element = element.parentElement;
}
element = element.children[1];
if (element.nodeName === 'INPUT') {
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
if (element.type === 'password') {
element.type = 'text';
this.type = 'text';
} else {
element.type = 'password';
this.type = 'password';
}
}
}

View File

@@ -3,7 +3,7 @@
<div>
@if ($currentState === 'welcome')
<h1 class="text-5xl font-bold">Welcome to Coolify</h1>
<p class="py-6 text-xl text-center">Let me help you to set the basics.</p>
<p class="py-6 text-xl text-center">Let me help you set up the basics.</p>
<div class="flex justify-center ">
<x-forms.button class="justify-center w-64 box" wire:click="$set('currentState','explanation')">Get
Started
@@ -24,12 +24,12 @@
<x-highlighted text="Self-hosting with superpowers!" /></span>
</x-slot:question>
<x-slot:explanation>
<p><x-highlighted text="Task automation:" /> You do not to manage your servers too much. Coolify do
<p><x-highlighted text="Task automation:" /> You don't need to manage your servers anymore. Coolify does
it for you.</p>
<p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your server, so
everything works without Coolify (except integrations and automations).</p>
<p><x-highlighted text="Monitoring:" />You will get notified on your favourite platform (Discord,
Telegram, Email, etc.) when something goes wrong, or an action needed from your side.</p>
<p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your servers, so
everything works without a connection to Coolify (except integrations and automations).</p>
<p><x-highlighted text="Monitoring:" />You can get notified on your favourite platforms (Discord,
Telegram, Email, etc.) when something goes wrong, or an action is needed from your side.</p>
</x-slot:explanation>
<x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:click="explanation">Next
@@ -40,8 +40,8 @@
@if ($currentState === 'select-server-type')
<x-boarding-step title="Server">
<x-slot:question>
Do you want to deploy your resources on your <x-highlighted text="Localhost" />
or on a <x-highlighted text="Remote Server" />?
Do you want to deploy your resources to your <x-highlighted text="Localhost" />
or to a <x-highlighted text="Remote Server" />?
</x-slot:question>
<x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:target="setServerType('localhost')"
@@ -297,12 +297,12 @@
You already have some projects. Do you want to use one of them or should I create a new one for
you?
@else
I will create an initial project for you. You can change all the details later on.
Let's create an initial project for you. You can change all the details later on.
@endif
</x-slot:question>
<x-slot:actions>
<x-forms.button class="justify-center w-64 box" wire:click="createNewProject">Let's create a new
one!</x-forms.button>
<x-forms.button class="justify-center w-64 box" wire:click="createNewProject">Create new
project!</x-forms.button>
<div>
@if (count($projects) > 0)
<form wire:submit='selectExistingProject' class="flex flex-col w-full gap-4 lg:w-96">
@@ -319,9 +319,9 @@
</div>
</x-slot:actions>
<x-slot:explanation>
<p>Projects are bound together several resources into one virtual group. There are no
limitations on the number of projects you could have.</p>
<p>Each project should have at least one environment. This helps you to create a production &
<p>Projects contain several resources combined into one virtual group. There are no
limitations on the number of projects you can add.</p>
<p>Each project should have at least one environment, this allows you to create a production &
staging version of the same application, but grouped separately.</p>
</x-slot:explanation>
</x-boarding-step>
@@ -331,7 +331,7 @@
@if ($currentState === 'create-resource')
<x-boarding-step title="Resources">
<x-slot:question>
I will redirect you to the new resource page, where you can create your first resource.
Let's go to the new resource page, where you can create your first resource.
</x-slot:question>
<x-slot:actions>
<div class="items-center justify-center w-64 box" wire:click="showNewResource">Let's do

View File

@@ -15,18 +15,9 @@
subscription is activated.<br> Please be patient.</span>
</div>
@endif
@if ($projects->count() === 0 && $servers->count() === 0)
No resources found. Add your first server & private key <a class="text-white underline"
href="{{ route('server.create') }}">here</a> or go to the <a class="text-white underline" href="{{ route('boarding') }}">boarding page</a>.
@endif
@if ($projects->count() > 0)
<h3 class="pb-4">Projects</h3>
@if ($projects->count() === 1)
<div class="grid grid-cols-1 gap-2">
@else
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@endif
@foreach ($projects as $project)
<h3 class="pb-4">Projects</h3>
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@forelse ($projects as $project)
<div class="gap-2 border border-transparent cursor-pointer box group">
@if (data_get($project, 'environments')->count() === 1)
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
@@ -61,88 +52,96 @@
</a>
</div>
</div>
@endforeach
</div>
@if ($projects->count() > 0)
@empty
<div>
No projects found. Add your first server <a class="text-white underline"
onclick="newEmptyProject.showModal()">here</a> or
go to the <a class="text-white underline" href="{{ route('onboarding') }}">onboarding page.</a>
<livewire:project.add-empty />
</div>
@endforelse
</div>
<h3 class="py-4">Servers</h3>
@endif
@if ($servers->count() === 1)
<div class="grid grid-cols-1 gap-2">
@else
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@endif
@foreach ($servers as $server)
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}" @class([
'gap-2 border cursor-pointer box group',
'border-transparent' => $server->settings->is_reachable,
'border-red-500' => !$server->settings->is_reachable,
])>
<div class="flex flex-col mx-6">
<div class="font-bold text-white">
{{ $server->name }}
</div>
<div class="description">
{{ $server->description }}</div>
<div class="flex gap-1 text-xs text-error">
@if (!$server->settings->is_reachable)
<span>Not reachable</span>
@endif
@if (!$server->settings->is_reachable && !$server->settings->is_usable)
&
@endif
@if (!$server->settings->is_usable)
<span>Not usable by Coolify</span>
@endif
</div>
</div>
<div class="flex-1"></div>
</a>
@endforeach
</div>
<div class="flex items-center gap-2">
<h3 class="py-4">Deployments</h3>
@if (count($deployments_per_server) > 0)
<x-loading />
@endif
<x-forms.button wire:click='cleanup_queue'>Cleanup Queues</x-forms.button>
</div>
<div wire:poll.1000ms="get_deployments" class="grid grid-cols-1">
@forelse ($deployments_per_server as $server_name => $deployments)
<h4 class="py-4">{{ $server_name }}</h4>
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
@foreach ($deployments as $deployment)
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
'gap-2 cursor-pointer box group border-l-2 border-dotted',
'border-coolgray-500' => data_get($deployment, 'status') === 'queued',
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@forelse ($servers as $server)
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}"
@class([
'gap-2 border cursor-pointer box group',
'border-transparent' => $server->settings->is_reachable,
'border-red-500' => !$server->settings->is_reachable,
])>
<div class="flex flex-col mx-6">
<div class="font-bold text-white">
{{ data_get($deployment, 'application_name') }}
</div>
@if (data_get($deployment, 'pull_request_id') !== 0)
<div class="description">
PR #{{ data_get($deployment, 'pull_request_id') }}
</div>
@endif
<div class="description">
{{ str(data_get($deployment, 'status'))->headline() }}
</div>
<div class="flex flex-col mx-6">
<div class="font-bold text-white">
{{ $server->name }}
</div>
<div class="flex-1"></div>
</a>
@endforeach
</div>
@empty
<div>No deployments running.</div>
@endforelse
</div>
@endif
<script>
function gotoProject(uuid, environment = 'production') {
window.location.href = '/project/' + uuid + '/' + environment;
}
</script>
{{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}
<div class="description">
{{ $server->description }}</div>
<div class="flex gap-1 text-xs text-error">
@if (!$server->settings->is_reachable)
<span>Not reachable</span>
@endif
@if (!$server->settings->is_reachable && !$server->settings->is_usable)
&
@endif
@if (!$server->settings->is_usable)
<span>Not usable by Coolify</span>
@endif
</div>
</div>
<div class="flex-1"></div>
</a>
@empty
<div>
No servers found.
Add your first server <a class="text-white underline" href="{{ route('server.create') }}">here</a> or
go to the <a class="text-white underline" href="{{ route('onboarding') }}">onboarding page.</a>
</div>
@endforelse
</div>
<div class="flex items-center gap-2">
<h3 class="py-4">Deployments</h3>
@if (count($deployments_per_server) > 0)
<x-loading />
@endif
<x-forms.button wire:click='cleanup_queue'>Cleanup Queues</x-forms.button>
</div>
<div wire:poll.1000ms="get_deployments" class="grid grid-cols-1">
@forelse ($deployments_per_server as $server_name => $deployments)
<h4 class="py-4">{{ $server_name }}</h4>
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
@foreach ($deployments as $deployment)
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
'gap-2 cursor-pointer box group border-l-2 border-dotted',
'border-coolgray-500' => data_get($deployment, 'status') === 'queued',
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
])>
<div class="flex flex-col mx-6">
<div class="font-bold text-white">
{{ data_get($deployment, 'application_name') }}
</div>
@if (data_get($deployment, 'pull_request_id') !== 0)
<div class="description">
PR #{{ data_get($deployment, 'pull_request_id') }}
</div>
@endif
<div class="description">
{{ str(data_get($deployment, 'status'))->headline() }}
</div>
</div>
<div class="flex-1"></div>
</a>
@endforeach
</div>
@empty
<div>No deployments running.</div>
@endforelse
</div>
<script>
function gotoProject(uuid, environment = 'production') {
window.location.href = '/project/' + uuid + '/' + environment;
}
</script>
{{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}
</div>

View File

@@ -5,9 +5,7 @@
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
</a>
</div>
<div class="flex items-center justify-center pb-4 text-center">
Set your initial password
</div>
<form class="flex flex-col gap-2" wire:submit='submit'>
<x-forms.input id="email" type="email" placeholder="Email" readonly label="Email" />
<x-forms.input id="password" type="password" placeholder="New Password" label="New Password" required />

View File

@@ -7,7 +7,8 @@
consider donating!</a>💜</span>
<span>It enables us to keep creating features without paywalls, ensuring our work remains free and
open.</span>
<x-forms.button class="bg-coolgray-400" wire:click='disable'>Disable This Popup</x-forms.button>
<x-forms.button class="bg-coolgray-400" wire:click='disableSponsorship'>Disable This
Popup</x-forms.button>
</div>
</div>
@endif
@@ -20,4 +21,16 @@
</div>
</x-banner>
@endif
@if (!currentTeam()->isAnyNotificationEnabled())
<div class="toast">
<div class="flex flex-col text-white rounded alert bg-coolgray-200">
<span><span class="font-bold text-red-500">WARNING:</span> No notifications enabled.<br><br> It is highly recommended to enable at least
one
notification channel to receive important alerts.<br>Visit <a href="{{ route('notification.index') }}"
class="text-white underline">/notification</a> to enable notifications.</span>
<x-forms.button class="bg-coolgray-400" wire:click='disableNotifications'>Disable This
Popup</x-forms.button>
</div>
</div>
@endif
</div>

View File

@@ -6,13 +6,22 @@
<h2>General</h2>
<x-forms.button type="submit" label="Save">Save</x-forms.button>
</div>
<div class="flex gap-2">
<div class="flex flex-col gap-2 lg:flex-row">
<x-forms.input id="name" label="Name" required />
<x-forms.input id="email" label="Email" readonly />
</div>
</form>
<h2 class="py-4">Subscription</h2>
<a href="{{ route('team.index') }}">Check in Team Settings</a>
<form wire:submit='resetPassword' class="flex flex-col max-w-xl pt-4">
<div class="flex items-center gap-2">
<h2>Reset Password</h2>
<x-forms.button type="submit" label="Save">Reset</x-forms.button>
</div>
<div class="flex flex-col gap-2">
<x-forms.input id="current_password" label="Current Password" required type="password" />
<x-forms.input id="new_password" label="New Password" required type="password" />
<x-forms.input id="new_password_confirmation" label="New Password Again" required type="password" />
</div>
</form>
<h2 class="py-4">Two-factor Authentication</h2>
@if (session('status') == 'two-factor-authentication-enabled')
<div class="mb-4 font-medium">

View File

@@ -45,11 +45,11 @@
</div>
@endif
@if ($application->build_pack === 'dockercompose')
<div class="w-96">
<x-forms.checkbox instantSave id="application.settings.is_raw_compose_deployment_enabled"
label="Raw Compose Deployment"
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a href='https://coolify.io/docs/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
</div>
<div class="w-96">
<x-forms.checkbox instantSave id="application.settings.is_raw_compose_deployment_enabled"
label="Raw Compose Deployment"
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a href='https://coolify.io/docs/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
</div>
@if (count($parsedServices) > 0 && !$application->settings->is_raw_compose_deployment_enabled)
@foreach (data_get($parsedServices, 'services') as $serviceName => $service)
@if (!isDatabaseImage(data_get($service, 'image')))
@@ -210,10 +210,10 @@
id="application.custom_docker_run_options" label="Custom Docker Options" />
@endif
@else
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='text-white underline' href='https://coolify.io/docs/custom-docker-options'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="application.custom_docker_run_options" label="Custom Docker Options" />
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='text-white underline' href='https://coolify.io/docs/custom-docker-options'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="application.custom_docker_run_options" label="Custom Docker Options" />
@endif
@if ($application->build_pack === 'dockercompose')
<x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button>
@@ -250,6 +250,21 @@
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"></x-forms.textarea>
<x-forms.button wire:click="resetDefaultLabels">Reset to Coolify Generated Labels</x-forms.button>
@endif
<h3 class="pt-8">Pre/Post Deployment Commands</h3>
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input id="application.pre_deployment_command" label="Pre-deployment Command"
helper="An optional script or command to execute in the existing container before the deployment begins." />
<x-forms.input id="application.pre_deployment_command_container" label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
</div>
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input placeholder="php artisan migrate" id="application.post_deployment_command"
label="Post-deployment Command"
helper="An optional script or command to execute in the newly built container after the deployment completes." />
<x-forms.input id="application.post_deployment_command_container" label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
</div>
</div>
</form>
</div>

View File

@@ -13,7 +13,10 @@
</x-modal>
<div class="pt-6">
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
<h3 class="py-4">Executions</h3>
<div class="flex items-center gap-2">
<h3 class="py-4">Executions</h3>
<x-forms.button wire:click='cleanupFailed'>Cleanup Failed Backups</x-forms.button>
</div>
<livewire:project.database.backup-executions :backup="$backup" :executions="$executions" />
</div>
</div>

View File

@@ -21,8 +21,8 @@
<div class='text-helper'>git@..</div>
</div>
<div class="flex gap-1">
<div>Preselect branch (eg: static):</div>
<div class='text-helper'>https://github.com/coollabsio/coolify-examples/tree/static</div>
<div>Preselect branch (eg: main):</div>
<div class='text-helper'>https://github.com/coollabsio/coolify-examples/tree/main</div>
</div>
<div>
For example application deployments, checkout <a class="text-white underline"

View File

@@ -289,10 +289,10 @@
</div>
@endforelse
</div>
@if ($isDatabase)
{{-- @if ($isDatabase)
<div class="text-center">Swarm clusters are excluded from this type of resource at the moment. It will
be activated soon. Stay tuned.</div>
@endif
@endif --}}
@endif
@if ($current_step === 'destinations')
<ul class="pb-10 steps">

View File

@@ -16,7 +16,7 @@
</div>
<div class="w-96">
<x-forms.checkbox instantSave id="service.connect_to_docker_network" label="Connect To Predefined Network"
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='text-white underline' href='https://coolify.io/docs/docker/compose#connect-to-predefined-networks'>this</a>." />
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='text-white underline' target='_blank' href='https://coolify.io/docs/docker/compose#connect-to-predefined-networks'>this</a>." />
</div>
@if ($fields)
<div>

View File

@@ -1,9 +1,11 @@
<form class="flex flex-col gap-2 rounded" wire:submit='submit'>
<x-forms.input placeholder="NODE_ENV" id="key" label="Name" required />
<x-forms.input placeholder="production" id="value" label="Value" required />
<x-forms.textarea x-show="$wire.is_multiline === true" x-cloak id="value" label="Value" required />
<x-forms.input x-show="$wire.is_multiline === false" x-cloak placeholder="production" id="value" x-bind:label="$wire.is_multiline === false && 'Value'" required />
@if (data_get($parameters, 'application_uuid'))
<x-forms.checkbox id="is_build_time" label="Build Variable?" />
@endif
@endif
<x-forms.checkbox id="is_multiline" label="Is Multiline?" />
<x-forms.button type="submit" @click="slideOverOpen=false">
Save
</x-forms.button>

View File

@@ -17,7 +17,7 @@
</div>
<div>Environment variables (secrets) for this resource.</div>
@if ($resource->type() === 'service')
<div>If you cannot find a variable here, or need a new one, define it in the Docker Compose file.</div>
<div>Hardcoded variables are not shown here.</div>
@endif
</div>
@if ($view === 'normal')

View File

@@ -20,10 +20,17 @@
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />
@endif
@else
<x-forms.input id="env.key" />
<x-forms.input type="password" id="env.value" />
@if ($env->is_multiline)
<x-forms.input isMultiline="{{ $env->is_multiline }}" id="env.key" />
<x-forms.textarea type="password" id="env.value" />
@else
<x-forms.input id="env.key" />
<x-forms.input type="password" id="env.value" />
@endif
@if ($env->is_shared)
<x-forms.input disabled type="password" id="env.real_value" />
@else
<x-forms.checkbox instantSave id="env.is_multiline" label="Is Multiline?" />
@endif
@if ($type !== 'service' && !$isSharedVariable)
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />

View File

@@ -7,16 +7,18 @@
<div class="pt-2" wire:loading wire:target="loadContainers">
Loading containers...
</div>
@foreach ($servers as $server)
@forelse ($servers as $server)
<h3 x-init="$wire.loadContainers({{ $server->id }})"></h3>
<div wire:loading.remove wire:target="loadContainers">
@forelse (data_get($server,'containers',[]) as $container)
<livewire:project.shared.get-logs :server="$server" :resource="$resource" :container="data_get($container,'Names')" />
<livewire:project.shared.get-logs :server="$server" :resource="$resource" :container="data_get($container, 'Names')" />
@empty
<div class="pt-2">No containers are not running on server: {{$server->name}}</div>
<div class="pt-2">No containers are not running on server: {{ $server->name }}</div>
@endforelse
</div>
@endforeach
@empty
<div>No functional server found for the application.</div>
@endforelse
</div>
@elseif ($type === 'database')
<h1>Logs</h1>
@@ -26,7 +28,11 @@
@if ($loop->first)
<h2 class="pb-4">Logs</h2>
@endif
<livewire:project.shared.get-logs :server="$servers[0]" :resource="$resource" :container="$container" />
@if (data_get($servers, '0'))
<livewire:project.shared.get-logs :server="data_get($servers, '0')" :resource="$resource" :container="$container" />
@else
<div> No functional server found for the database.</div>
@endif
@empty
<div class="pt-2">No containers are not running.</div>
@endforelse
@@ -37,7 +43,11 @@
@if ($loop->first)
<h2 class="pb-4">Logs</h2>
@endif
<livewire:project.shared.get-logs :server="$servers[0]" :resource="$resource" :container="$container" />
@if (data_get($servers, '0'))
<livewire:project.shared.get-logs :server="data_get($servers, '0')" :resource="$resource" :container="$container" />
@else
<div> No functional server found for the service.</div>
@endif
@empty
<div class="pt-2">No containers are not running.</div>
@endforelse

View File

@@ -1,16 +1,29 @@
<div>
@if (data_get($server, 'proxy.type'))
@if ($server->proxyType())
<div x-init="$wire.loadProxyConfiguration">
@if ($selectedProxy === 'TRAEFIK_V2')
@if ($selectedProxy !== 'NONE')
<form wire:submit='submit'>
<div class="flex items-center gap-2">
<h2>Configuration</h2>
<x-forms.button type="submit">Save</x-forms.button>
@if ($server->proxy->status === 'exited')
<x-forms.button wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
@else
<x-forms.button disabled wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
@endif
<x-forms.button type="submit">Save</x-forms.button>
</div>
<div class="pt-3 pb-4 ">Traefik v2</div>
<div class="pb-4 "> <svg class="inline-flex w-6 h-6 mr-2 text-warning" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
</svg>Before switching proxies, please read <a class="text-white underline"
href="https://coolify.io/docs/server/switching-proxies">this</a>.</div>
@if ($server->proxyType() === 'TRAEFIK_V2')
<div class="pb-4">Traefik v2</div>
@elseif ($server->proxyType() === 'CADDY')
<div class="pb-4 ">Caddy</div>
@endif
@if (
$server->proxy->last_applied_settings &&
$server->proxy->last_saved_settings !== $server->proxy->last_applied_settings)
@@ -26,7 +39,7 @@
<div wire:loading.remove wire:target="loadProxyConfiguration">
@if ($proxy_settings)
<div class="flex flex-col gap-2 pt-4">
<x-forms.textarea label="Configuration file: traefik.conf" name="proxy_settings"
<x-forms.textarea label="Configuration file" name="proxy_settings"
wire:model="proxy_settings" rows="30" />
<x-forms.button wire:click.prevent="reset_proxy_configuration">
Reset configuration to default
@@ -40,7 +53,7 @@
<h2>Configuration</h2>
<x-forms.button wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
</div>
<div class="pt-3 pb-4">Custom (None) Proxy Selected</div>
<div class="pt-2 pb-4">Custom (None) Proxy Selected</div>
@else
<div class="flex items-center gap-2">
<h2>Configuration</h2>
@@ -57,14 +70,13 @@
</x-forms.button>
<x-forms.button class="box" wire:click="select_proxy('TRAEFIK_V2')">
Traefik
v2
</x-forms.button>
<x-forms.button class="box" wire:click="select_proxy('CADDY')">
Caddy (experimental)
</x-forms.button>
<x-forms.button disabled class="box">
Nginx
</x-forms.button>
<x-forms.button disabled class="box">
Caddy
</x-forms.button>
</div>
</div>
@endif

View File

@@ -16,10 +16,10 @@
</p>
</x-slot:modalBody>
</x-modal>
@if ($server->isFunctional() && data_get($server, 'proxy.type') !== 'NONE')
@if ($server->isFunctional() && $server->proxyType() !== 'NONE')
@if (data_get($server, 'proxy.status') === 'running')
<div class="flex gap-4">
@if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable)
@if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable && $server->proxyType() === 'TRAEFIK_V2')
<button>
<a target="_blank" href="http://{{ $serverIp }}:8080">
Traefik Dashboard

View File

@@ -19,7 +19,7 @@
Add</button>
</x-slide-over>
</div>
<div class='pb-4'>You can add dynamic Traefik configurations here.</div>
<div class='pb-4'>You can add dynamic proxy configurations here.</div>
</div>
</div>
<div wire:loading wire:target="loadDynamicConfigurations">
@@ -29,12 +29,12 @@
@if ($contents?->isNotEmpty())
@foreach ($contents as $fileName => $value)
<div class="flex flex-col gap-2 py-2">
@if (str_replace('|', '.', $fileName) === 'coolify.yaml')
@if (str_replace('|', '.', $fileName) === 'coolify.yaml' || str_replace('|', '.', $fileName) === 'Caddyfile' || str_replace('|', '.', $fileName) === 'coolify.caddy' || str_replace('|', '.', $fileName) === 'default_redirect_404.caddy')
<div>
<h3 class="text-white">File: {{ str_replace('|', '.', $fileName) }}</h3>
</div>
<x-forms.textarea disabled name="proxy_settings"
wire:model="contents.{{ $fileName }}" rows="10" />
wire:model="contents.{{ $fileName }}" rows="5" />
@else
<livewire:server.proxy.dynamic-configuration-navbar :server_id="$server->id"
:fileName="$fileName" :value="$value" :newFile="false"

View File

@@ -1,5 +1,5 @@
<form wire:submit.prevent="addDynamicConfiguration" class="flex flex-col gap-4">
<x-forms.input id="fileName" label="Filename (.yaml or .yml)" required />
<x-forms.input id="fileName" label="Filename" required />
<x-forms.textarea id="value" label="Configuration" required rows="20" />
<x-forms.button type="submit" @click="slideOverOpen=false">Save</x-forms.button>
</form>

View File

@@ -45,7 +45,8 @@
{{ data_get($resource, 'environment.name') }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap"><a class=""
href="{{ $resource->link() }}">{{ $resource->name }} <x-internal-link/></a>
href="{{ $resource->link() }}">{{ $resource->name }}
<x-internal-link /></a>
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ str($resource->type())->headline() }}</td>
@@ -138,6 +139,4 @@
</div>
</div>
</div>
</div>

View File

@@ -69,7 +69,7 @@
<x-forms.input id="github_app.webhook_secret" label="Webhook Secret" type="password" />
</div>
<div class="flex items-end gap-2 ">
<h3 class="pt-4">Permissions</h3>
<h2 class="pt-4">Permissions</h2>
<x-forms.button wire:click.prevent="checkPermissions">Refetch</x-forms.button>
<a href="{{ get_permissions_path($github_app) }}">
<x-forms.button>
@@ -93,6 +93,57 @@
</div>
@endif
</form>
<div class="w-full pt-10">
<div class="h-full">
<div class="flex flex-col">
<div class="flex gap-2">
<h2>Resources</h2>
</div>
<div class="pb-4 title">Here you can find all resources that are used by this source.</div>
</div>
<div class="flex flex-col">
<div class="flex flex-col">
<div class="overflow-x-auto">
<div class="inline-block min-w-full">
<div class="overflow-hidden">
<table class="min-w-full divide-y divide-coolgray-400">
<thead>
<tr class="text-neutral-500">
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Project
</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">
Environment</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Name</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Type</th>
</tr>
</thead>
<tbody class="divide-y divide-coolgray-400">
@forelse ($applications->sortBy('name',SORT_NATURAL) as $resource)
<tr class="text-white bg-coolblack hover:bg-coolgray-100">
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ data_get($resource->project(), 'name') }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ data_get($resource, 'environment.name') }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap"><a class=""
href="{{ $resource->link() }}">{{ $resource->name }}
<x-internal-link /></a>
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ str($resource->type())->headline() }}</td>
</tr>
@empty
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@else
<div class="flex items-center gap-2 pb-4">
<h1>GitHub App</h1>

View File

@@ -1,5 +1,5 @@
<div>
<x-team.navbar />
<x-notifications.navbar />
<h2 class="pb-4">Notifications</h2>
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'email' }" class="flex h-full">
<div class="flex flex-col gap-4 min-w-fit">