Merge branch 'coollabsio:main' into fix-redis-db-ui

This commit is contained in:
peaklabs-dev
2024-09-26 20:02:05 +02:00
committed by GitHub
364 changed files with 13628 additions and 4875 deletions

View File

@@ -4,3 +4,18 @@
// const app = createApp({});
// app.component("magic-bar", MagicBar);
// app.mount("#vue");
import { Terminal } from '@xterm/xterm';
import '@xterm/xterm/css/xterm.css';
import { FitAddon } from '@xterm/addon-fit';
if (!window.term) {
window.term = new Terminal({
cols: 80,
rows: 30,
fontFamily: '"Fira Code", courier-new, courier, monospace, "Powerline Extra Symbols"',
cursorBlink: true,
});
window.fitAddon = new FitAddon();
window.term.loadAddon(window.fitAddon);
}

View File

@@ -390,7 +390,7 @@ const magicActions = [{
},
{
id: 19,
name: 'Goto: Command Center',
name: 'Goto: Terminal',
icon: 'goto',
sequence: ['main', 'redirect']
},
@@ -653,7 +653,7 @@ async function redirect() {
targetUrl.pathname = `/settings`
break;
case 19:
targetUrl.pathname = `/command-center`
targetUrl.pathname = `/terminal`
break;
case 20:
targetUrl.pathname = `/team/notifications`

View File

@@ -1,16 +1,32 @@
<div class="flex flex-row items-center gap-4 px-2 py-1 form-control min-w-fit dark:hover:bg-coolgray-100">
<label class="flex gap-4 px-0 min-w-fit label">
<span class="flex gap-2">
@if ($label)
{!! $label !!}
@else
{{ $id }}
@endif
@if ($helper)
<x-helper :helper="$helper" />
@endif
</span>
</label>
@props([
'id',
'label' => null,
'helper' => null,
'disabled' => false,
'instantSave' => false,
'value' => null,
'hideLabel' => false,
'fullWidth' => false,
])
<div @class([
'flex flex-row items-center gap-4 px-2 py-1 form-control min-w-fit dark:hover:bg-coolgray-100',
'w-full' => $fullWidth,
])>
@if (!$hideLabel)
<label class="flex gap-4 px-0 min-w-fit label">
<span class="flex gap-2">
@if ($label)
{!! $label !!}
@else
{{ $id }}
@endif
@if ($helper)
<x-helper :helper="$helper" />
@endif
</span>
</label>
@endif
<span class="flex-grow"></span>
<input @disabled($disabled) type="checkbox" {{ $attributes->merge(['class' => $defaultClass]) }}
@if ($instantSave) wire:loading.attr="disabled" wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}'

View File

@@ -3,7 +3,7 @@
'w-full' => !$isMultiline,
])>
@if ($label)
<label class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }}
<label class="flex gap-1 items-center mb-1 text-sm font-medium">{{ $label }}
@if ($required)
<x-highlighted text="*" />
@endif
@@ -16,7 +16,7 @@
<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:dark:text-white">
class="flex absolute inset-y-0 right-0 items-center pr-2 cursor-pointer hover:dark: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" />

View File

@@ -1,6 +1,6 @@
<div class="w-full">
@if ($label)
<label class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }}
<label class="flex gap-1 items-center mb-1 text-sm font-medium">{{ $label }}
@if ($required)
<x-highlighted text="*" />
@endif
@@ -9,7 +9,8 @@
@endif
</label>
@endif
<select {{ $attributes->merge(['class' => $defaultClass]) }} @required($required) wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
<select {{ $attributes->merge(['class' => $defaultClass]) }} @required($required)
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled" name={{ $id }}
@if ($attributes->whereStartsWith('wire:model')->first()) {{ $attributes->whereStartsWith('wire:model')->first() }} @else wire:model={{ $id }} @endif>
{{ $slot }}

View File

@@ -1,14 +1,105 @@
@props([
'title' => 'Are you sure?',
'isErrorButton' => false,
'buttonTitle' => 'REWRITE THIS BUTTON TITLE PLEASSSSEEEE',
'buttonTitle' => 'Confirm Action',
'buttonFullWidth' => false,
'customButton' => null,
'disabled' => false,
'action' => 'delete',
'submitAction' => 'delete',
'content' => null,
'checkboxes' => [],
'actions' => [],
'confirmWithText' => true,
'confirmationText' => 'Confirm Deletion',
'confirmationLabel' => 'Please confirm the execution of the actions by entering the Name below',
'shortConfirmationLabel' => 'Name',
'confirmWithPassword' => true,
'step1ButtonText' => 'Continue',
'step2ButtonText' => 'Continue',
'step3ButtonText' => 'Confirm',
'dispatchEvent' => false,
'dispatchEventType' => 'success',
'dispatchEventMessage' => '',
])
<div x-data="{ modalOpen: false }" @keydown.escape.window="modalOpen = false" :class="{ 'z-40': modalOpen }"
<div x-data="{
modalOpen: false,
step: {{ empty($checkboxes) ? 2 : 1 }},
initialStep: {{ empty($checkboxes) ? 2 : 1 }},
finalStep: {{ $confirmWithPassword ? 3 : 2 }},
deleteText: '',
password: '',
actions: @js($actions),
confirmationText: @js($confirmationText),
userConfirmationText: '',
confirmWithText: @js($confirmWithText),
confirmWithPassword: @js($confirmWithPassword),
copied: false,
submitAction: @js($submitAction),
passwordError: '',
selectedActions: @js(collect($checkboxes)->pluck('id')->filter(fn($id) => $this->$id)->values()->all()),
dispatchEvent: @js($dispatchEvent),
dispatchEventType: @js($dispatchEventType),
dispatchEventMessage: @js($dispatchEventMessage),
resetModal() {
this.step = this.initialStep;
this.deleteText = '';
this.password = '';
this.userConfirmationText = '';
this.selectedActions = @js(collect($checkboxes)->pluck('id')->filter(fn($id) => $this->$id)->values()->all());
$wire.$refresh();
},
step1ButtonText: @js($step1ButtonText),
step2ButtonText: @js($step2ButtonText),
step3ButtonText: @js($step3ButtonText),
validatePassword() {
if (this.confirmWithPassword && !this.password) {
return 'Password is required.';
}
return '';
},
submitForm() {
if (this.confirmWithPassword) {
this.passwordError = this.validatePassword();
if (this.passwordError) {
return Promise.resolve(this.passwordError);
}
}
const methodName = this.submitAction.split('(')[0];
const paramsMatch = this.submitAction.match(/\((.*?)\)/);
const params = paramsMatch ? paramsMatch[1].split(',').map(param => param.trim()) : [];
if (this.confirmWithPassword) {
params.push(this.password);
}
params.push(this.selectedActions);
return $wire[methodName](...params)
.then(result => {
if (result === true) {
return true;
} else if (typeof result === 'string') {
return result;
}
});
},
copyConfirmationText() {
navigator.clipboard.writeText(this.confirmationText);
this.copied = true;
setTimeout(() => {
this.copied = false;
}, 2000);
},
toggleAction(id) {
const index = this.selectedActions.indexOf(id);
if (index > -1) {
this.selectedActions.splice(index, 1);
} else {
this.selectedActions.push(id);
}
}
}" @keydown.escape.window="modalOpen = false; resetModal()" :class="{ 'z-40': modalOpen }"
class="relative w-auto h-auto">
@if ($customButton)
@if ($buttonFullWidth)
@@ -48,7 +139,7 @@
@endif
@else
@if ($buttonFullWidth)
<x-forms.button @click="modalOpen=true" class="flex w-full gap-2" wire:target>
<x-forms.button @click="modalOpen=true" class="flex gap-2 w-full" wire:target>
{{ $buttonTitle }}
</x-forms.button>
@else
@@ -60,11 +151,9 @@
@endif
@endif
<template x-teleport="body">
<div x-show="modalOpen"
<div x-show="modalOpen" @click.away="modalOpen = false; resetModal()"
class="fixed top-0 lg:pt-10 left-0 z-[99] flex items-start justify-center w-screen h-screen" x-cloak>
<div x-show="modalOpen" x-transition:enter="ease-out duration-100" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-100"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" @click="modalOpen=false"
<div x-show="modalOpen" @click="modalOpen = false; resetModal()"
class="absolute inset-0 w-full h-full bg-black bg-opacity-20 backdrop-blur-sm"></div>
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
@@ -72,56 +161,182 @@
x-transition:leave="ease-in duration-100"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
class="relative w-full py-6 border rounded min-w-full lg:min-w-[36rem] max-w-fit bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
<div class="flex items-center justify-between pb-3">
<h3 class="text-2xl font-bold">{{ $title }}</h3>
{{-- <button @click="modalOpen=false"
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 rounded-full dark:text-white hover:bg-coolgray-300">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
class="relative w-full py-6 border rounded min-w-full lg:min-w-[36rem] max-w-[48rem] bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
<div class="flex justify-between items-center pb-3">
<h3 class="pr-8 text-2xl font-bold">{{ $title }}</h3>
<button @click="modalOpen = false; resetModal()"
class="flex absolute top-2 right-2 justify-center items-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button> --}}
</button>
</div>
<div class="relative w-auto pb-8">
{{ $slot }}
</div>
<div class="flex flex-row justify-end space-x-2">
<x-forms.button @click="modalOpen=false"
class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
</x-forms.button>
<div class="flex-1"></div>
@if ($attributes->whereStartsWith('wire:click')->first())
@if ($isErrorButton)
<x-forms.button @click="modalOpen=false" class="w-24" isError type="button"
wire:click.prevent="{{ $attributes->get('wire:click') }}">Continue
</x-forms.button>
@else
<x-forms.button @click="modalOpen=false" class="w-24" isHighlighted type="button"
wire:click.prevent="{{ $attributes->get('wire:click') }}">Continue
</x-forms.button>
@endif
@elseif ($attributes->whereStartsWith('@click')->first())
@if ($isErrorButton)
<x-forms.button class="w-24" isError type="button"
@click="modalOpen=false;{{ $attributes->get('@click') }}">Continue
</x-forms.button>
@else
<x-forms.button class="w-24" isHighlighted type="button"
@click="modalOpen=false;{{ $attributes->get('@click') }}">Continue
</x-forms.button>
@endif
@elseif ($action)
@if ($isErrorButton)
<x-forms.button @click="modalOpen=false" class="w-24" isError type="button"
wire:click.prevent="{{ $action }}">Continue
</x-forms.button>
@else
<x-forms.button @click="modalOpen=false" class="w-24" isHighlighted type="button"
wire:click.prevent="{{ $action }}">Continue
</x-forms.button>
@endif
<div class="relative pb-8 w-auto">
@if (!empty($checkboxes))
<!-- Step 1: Select actions -->
<div x-show="step === 1">
<div class="flex justify-between items-center">
<h4>Actions</h4>
</div>
@foreach ($checkboxes as $index => $checkbox)
<div class="flex justify-between items-center mb-2">
<x-forms.checkbox fullWidth :label="$checkbox['label']" :id="$checkbox['id']"
:wire:model="$checkbox['id']"
x-on:change="toggleAction('{{ $checkbox['id'] }}')" :checked="$this->{$checkbox['id']}"
x-bind:checked="selectedActions.includes('{{ $checkbox['id'] }}')" />
</div>
@endforeach
</div>
@endif
<!-- Step 2: Confirm deletion -->
<div x-show="step === 2">
<div class="p-4 mb-4 text-white border-l-4 border-red-500 bg-error" role="alert">
<p class="font-bold">Warning</p>
<p>This operation is permanent and cannot be undone. Please think again before proceeding!
</p>
</div>
<div class="mb-4">The following actions will be performed:</div>
<ul class="mb-4 space-y-2">
@foreach ($actions as $action)
<li class="flex items-center text-red-500">
<svg class="flex-shrink-0 mr-2 w-5 h-5" fill="none" stroke="currentColor"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18L18 6M6 6l12 12"></path>
</svg>
<span>{{ $action }}</span>
</li>
@endforeach
@foreach ($checkboxes as $checkbox)
<template x-if="selectedActions.includes('{{ $checkbox['id'] }}')">
<li class="flex items-center text-red-500">
<svg class="flex-shrink-0 mr-2 w-5 h-5" fill="none" stroke="currentColor"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18L18 6M6 6l12 12"></path>
</svg>
<span>{{ $checkbox['label'] }}</span>
</li>
</template>
@endforeach
</ul>
@if ($confirmWithText)
<div class="mb-4">
<h4 class="mb-2 text-lg font-semibold">Confirm Actions</h4>
<p class="mb-2 text-sm">{{ $confirmationLabel }}</p>
<div class="relative mb-2">
<input type="text" x-model="confirmationText"
class="p-2 pr-10 w-full text-black rounded cursor-text input" readonly>
<button @click="copyConfirmationText()"
class="absolute right-2 top-1/2 text-gray-500 transform -translate-y-1/2 hover:text-gray-700"
title="Copy confirmation text" x-ref="copyButton">
<template x-if="!copied">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 20 20"
fill="currentColor">
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
<path
d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
</svg>
</template>
<template x-if="copied">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-green-500"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</template>
</button>
</div>
<label for="userConfirmationText"
class="block mt-4 text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $shortConfirmationLabel }}
</label>
<input type="text" x-model="userConfirmationText"
class="p-2 mt-1 w-full text-black rounded input">
</div>
@endif
</div>
<!-- Step 3: Password confirmation -->
<div x-show="step === 3 && confirmWithPassword">
<div class="p-4 mb-4 text-white border-l-4 border-red-500 bg-error" role="alert">
<p class="font-bold">Final Confirmation</p>
<p>Please enter your password to confirm this destructive action.</p>
</div>
<div class="flex flex-col gap-2 mb-4">
<label for="password-confirm"
class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Your Password
</label>
<input type="password" id="password-confirm" x-model="password" class="w-full input"
placeholder="Enter your password">
<p x-show="passwordError" x-text="passwordError" class="mt-1 text-sm text-red-500"></p>
@error('password')
<p class="mt-1 text-sm text-red-500">{{ $message }}</p>
@enderror
</div>
</div>
</div>
<!-- Navigation buttons -->
<div class="flex flex-wrap gap-2 justify-between mt-4">
<template x-if="step > initialStep">
<x-forms.button @click="step--" class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
Back
</x-forms.button>
</template>
<template x-if="step === initialStep">
<x-forms.button @click="modalOpen = false; resetModal()"
class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
Cancel
</x-forms.button>
</template>
<template x-if="step === 1">
<x-forms.button @click="step++" class="w-auto" isError>
<span x-text="step1ButtonText"></span>
</x-forms.button>
</template>
<template x-if="step === 2">
<x-forms.button x-bind:disabled="confirmWithText && userConfirmationText !== confirmationText"
class="w-auto" isError
@click="
if (dispatchEvent) {
$wire.dispatch(dispatchEventType, dispatchEventMessage);
}
if (confirmWithPassword) {
step++;
} else {
modalOpen = false;
resetModal();
submitForm();
}">
<span x-text="step2ButtonText"></span>
</x-forms.button>
</template>
<template x-if="step === 3 && confirmWithPassword">
<x-forms.button x-bind:disabled="!password" class="w-auto" isError
@click="
if (dispatchEvent) {
$wire.dispatch(dispatchEventType, dispatchEventMessage);
}
submitForm().then((result) => {
if (result === true) {
modalOpen = false;
resetModal();
} else {
passwordError = result;
}
});
">
<span x-text="step3ButtonText"></span>
</x-forms.button>
</template>
</div>
</div>
</div>

View File

@@ -1,7 +1,7 @@
<nav class="flex flex-col flex-1 bg-white border-r dark:border-coolgray-200 dark:bg-base" x-data="{
switchWidth() {
if (this.full === 'full') {
localStorage.removeItem('pageWidth');
localStorage.setItem('pageWidth', 'center');
} else {
localStorage.setItem('pageWidth', 'full');
}
@@ -74,8 +74,10 @@
<button @click="setTheme('light')" class="px-1 dropdown-item-no-padding">Light</button>
<button @click="setTheme('system')" class="px-1 dropdown-item-no-padding">System</button>
<div class="my-1 font-bold border-b dark:border-coolgray-500 dark:text-white text-md">Width</div>
<button @click="switchWidth()" class="px-1 dropdown-item-no-padding" x-show="full">Center</button>
<button @click="switchWidth()" class="px-1 dropdown-item-no-padding" x-show="!full">Full</button>
<button @click="switchWidth()" class="px-1 dropdown-item-no-padding"
x-show="full === 'full'">Center</button>
<button @click="switchWidth()" class="px-1 dropdown-item-no-padding"
x-show="full === 'center'">Full</button>
</div>
</x-dropdown>
</div>
@@ -226,9 +228,9 @@
</a>
</li>
<li>
<a title="Command Center"
class="{{ request()->is('command-center*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('command-center') }}">
<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"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
@@ -236,7 +238,7 @@
<path d="M5 7l5 5l-5 5" />
<path d="M12 19l7 0" />
</svg>
Command Center
Terminal
</a>
</li>
<li>

View File

@@ -2,7 +2,9 @@
<livewire:server.proxy.modal :server="$server" />
<div class="flex items-center gap-2">
<h1>Server</h1>
<livewire:server.proxy.status :server="$server" />
@if ($server->proxySet())
<livewire:server.proxy.status :server="$server" />
@endif
</div>
<div class="subtitle">{{ data_get($server, 'name') }}.</div>
<div class="navbar-main">

View File

@@ -1,20 +1,18 @@
<div class="flex h-full pr-4">
<div class="flex flex-col w-48 gap-4 min-w-fit">
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
href="{{ route('server.proxy', $parameters) }}">
<button>Configuration</button>
</a>
@if ($server->proxyType() !== 'NONE')
{{-- @if ($server->proxyType() === 'TRAEFIK') --}}
@if ($server->proxySet())
<div class="flex h-full pr-4">
<div class="flex flex-col w-48 gap-4 min-w-fit">
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
href="{{ route('server.proxy', $parameters) }}">
<button>Configuration</button>
</a>
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'dark:text-white' : '' }}"
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
<button>Dynamic Configurations</button>
</a>
{{-- @endif --}}
<a class="{{ request()->routeIs('server.proxy.logs') ? 'dark:text-white' : '' }}"
href="{{ route('server.proxy.logs', $parameters) }}">
<button>Logs</button>
</a>
@endif
</div>
</div>
</div>
@endif

View File

@@ -1,5 +1,5 @@
<x-emails.layout>
Your server ({{ $name }}) disabled because it is not paid! All automations and integrations are stopped.
Your server ({{ $name }}) disabled because it is not paid! All automations and integrations are stopped.
Please update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions).
Please update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).
</x-emails.layout>

View File

@@ -9,6 +9,10 @@
open: false,
init() {
this.pageWidth = localStorage.getItem('pageWidth');
if (!this.pageWidth) {
this.pageWidth = 'full';
localStorage.setItem('pageWidth', 'full');
}
}
}" x-cloak class="mx-auto" :class="pageWidth === 'full' ? '' : 'max-w-7xl'">
<div class="relative z-50 lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true">

View File

@@ -28,25 +28,33 @@
<x-highlighted text="Self-hosting with superpowers!" /></span>
</x-slot:question>
<x-slot:explanation>
<p><x-highlighted text="Task automation:" /> You don't need to manage your servers anymore.
<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 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
it for you.
</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>
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 lg:w-64 box-boarding" wire:click="explanation">Next
<x-forms.button class="justify-center w-64 box-boarding" wire:click="explanation">Next
</x-forms.button>
</x-slot:actions>
</x-boarding-step>
@elseif ($currentState === 'select-server-type')
<x-boarding-step title="Server">
<x-slot:question>
Do you want to deploy your resources to your <x-highlighted text="Localhost" />
or to 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-boarding" wire:target="setServerType('localhost')"
@@ -56,32 +64,66 @@
<x-forms.button class="justify-center w-64 box-boarding " wire:target="setServerType('remote')"
wire:click="setServerType('remote')">Remote Server
</x-forms.button>
@if (!$serverReachable)
Localhost is not reachable with the following public key.
<br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for
user or skip the boarding process and add a new private key manually to Coolify and to the
server.
<br />
Check this <a target="_blank" class="underline"
href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further
help.
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.button class="lg:w-64 box-boarding" wire:target="setServerType('localhost')"
wire:click="setServerType('localhost')">Check again
</x-forms.button>
<div class="mt-6 p-4 border border-error rounded-lg text-gray-800 dark:text-gray-200">
<h2 class="text-lg font-bold mb-2">Server is not reachable</h2>
<p class="mb-4">Please check the connection details below and correct them if they are
incorrect.</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<x-forms.input placeholder="Default is 22" label="Port" id="remoteServerPort"
wire:model="remoteServerPort" :value="$remoteServerPort" />
<div>
<x-forms.input placeholder="Default is root" label="User" id="remoteServerUser"
wire:model="remoteServerUser" :value="$remoteServerUser" />
<p class="text-xs mt-1">
Non-root user is experimental:
<a class="font-bold underline" target="_blank"
href="https://coolify.io/docs/knowledge-base/server/non-root-user">docs</a>
</p>
</div>
</div>
<div class="mb-4">
<p class="mb-2">If the connection details are correct, please ensure:</p>
<ul class="list-disc list-inside">
<li>The correct public key is in your <code
class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
file for the specified user</li>
<li>Or skip the boarding process and manually add a new private key to Coolify and
the server</li>
</ul>
</div>
<p class="mb-4">
For more help, check this <a target="_blank" class="underline font-semibold"
href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a>.
</p>
<x-forms.input readonly id="serverPublicKey" class="mb-4"
label="Current Public Key"></x-forms.input>
<x-forms.button class="w-full box-boarding" wire:click="saveAndValidateServer">
Check Again
</x-forms.button>
</div>
@endif
</x-slot:actions>
<x-slot:explanation>
<p>Servers are the main building blocks, as they will host your applications, databases,
services, called resources. Any CPU intensive process will use the server's CPU where you
are deploying your resources.</p>
<p><x-highlighted text="Localhost" /> is the server where Coolify is running on. It is not
<p>
<x-highlighted text="Localhost" /> is the server where Coolify is running on. It is not
recommended to use one server
for everything.</p>
<p><x-highlighted text="A remote server" /> is a server reachable through SSH. It can be hosted
for everything.
</p>
<p>
<x-highlighted text="A remote server" /> is a server reachable through SSH. It can be hosted
at home, or from any cloud
provider.</p>
provider.
</p>
</x-slot:explanation>
</x-boarding-step>
@elseif ($currentState === 'private-key')
@@ -126,10 +168,7 @@
<div class="flex flex-col gap-4">
<div>
<x-forms.button class="justify-center w-64 box-boarding" wire:click="createNewServer">No
(create
one
for
me)
(create one for me)
</x-forms.button>
</div>
<div>
@@ -145,20 +184,48 @@
</div>
</div>
@if (!$serverReachable)
This server is not reachable with the following public key.
<br /> <br />
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for
user or skip the boarding process and add a new private key manually to Coolify and to the
server.
<br />
Check this <a target="_blank" class="underline"
href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further
help.
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
<x-forms.button class="w-64 box-boarding" wire:target="validateServer"
wire:click="validateServer">Check
again
</x-forms.button>
<div class="mt-6 p-4 bg-red-100 dark:bg-red-950 rounded-lg text-gray-800 dark:text-gray-200">
<h2 class="text-lg font-bold mb-2">Server is not reachable</h2>
<p class="mb-4">Please check the connection details below and correct them if they are
incorrect.</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<x-forms.input placeholder="Default is 22" label="Port" id="remoteServerPort"
wire:model="remoteServerPort" :value="$remoteServerPort" />
<div>
<x-forms.input placeholder="Default is root" label="User" id="remoteServerUser"
wire:model="remoteServerUser" :value="$remoteServerUser" />
<p class="text-xs mt-1">
Non-root user is experimental:
<a class="font-bold underline" target="_blank"
href="https://coolify.io/docs/knowledge-base/server/non-root-user">docs</a>
</p>
</div>
</div>
<div class="mb-4">
<p class="mb-2">If the connection details are correct, please ensure:</p>
<ul class="list-disc list-inside">
<li>The correct public key is in your <code
class="bg-red-200 dark:bg-red-900 px-1 rounded">~/.ssh/authorized_keys</code>
file for the specified user</li>
<li>Or skip the boarding process and manually add a new private key to Coolify and
the server</li>
</ul>
</div>
<p class="mb-4">
For more help, check this <a target="_blank" class="underline font-semibold"
href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a>.
</p>
<x-forms.input readonly id="serverPublicKey" class="mb-4"
label="Current Public Key"></x-forms.input>
<x-forms.button class="w-full md:w-auto box-boarding" wire:click="saveAndValidateServer">
Check again
</x-forms.button>
</div>
@endif
</x-slot:actions>
<x-slot:explanation>
@@ -180,8 +247,8 @@
label="Name" id="privateKeyName" />
<x-forms.input placeholder="Description, so others will know more about this."
label="Description" id="privateKeyDescription" />
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key"
id="privateKey" />
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
label="Private Key" id="privateKey" />
@if ($privateKeyType === 'create')
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
<span class="font-bold dark:text-warning">ACTION REQUIRED: Copy the 'Public Key' to your
@@ -206,31 +273,32 @@
Please let me know your server details.
</x-slot:question>
<x-slot:actions>
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 lg:pr-10">
<div class="flex flex-col gap-2 lg:flex-row">
<x-forms.input required placeholder="Choose a name for your Server. Could be anything."
label="Name" id="remoteServerName" />
<x-forms.input placeholder="Description, so others will know more about this."
label="Description" id="remoteServerDescription" />
</div>
<div class="flex flex-col gap-2 lg:flex-row ">
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost" />
<x-forms.input required placeholder="Port number of your server. Default is 22."
label="Port" id="remoteServerPort" />
<div class="w-full">
<x-forms.input required placeholder="User to connect to your server. Default is root."
label="User" id="remoteServerUser" />
<div class="text-xs dark:text-warning text-coollabs ">Non-root user is experimental: <a
class="font-bold underline" target="_blank"
href="https://coolify.io/docs/knowledge-base/server/non-root-user">docs</a>.
<form wire:submit='saveServer' class="flex flex-col w-full gap-4 lg:w-96">
<x-forms.input required placeholder="Choose a name for your Server. Could be anything."
label="Name" id="remoteServerName" wire:model="remoteServerName" />
<x-forms.input placeholder="Description, so others will know more about this."
label="Description" id="remoteServerDescription"
wire:model="remoteServerDescription" />
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost"
wire:model="remoteServerHost" />
<div x-data="{ showAdvanced: false }" class="flex flex-col gap-2">
<button @click="showAdvanced = !showAdvanced" type="button"
class="text-left text-sm text-gray-600 dark:text-gray-300 hover:underline">
Advanced Settings
</button>
<div x-show="showAdvanced" class="flex flex-col gap-2">
<x-forms.input placeholder="Port number of your server. Default is 22." label="Port"
id="remoteServerPort" wire:model="remoteServerPort" />
<div>
<x-forms.input placeholder="Default is root." label="User"
id="remoteServerUser" wire:model="remoteServerUser" />
<div class="text-xs text-gray-600 dark:text-gray-300">Non-root user is
experimental: <a class="font-bold underline" target="_blank"
href="https://coolify.io/docs/knowledge-base/server/non-root-user">docs</a>.
</div>
</div>
</div>
</div>
<div class="lg:w-64">
<x-forms.checkbox
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='dark:text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
id="isCloudflareTunnel" label="Cloudflare Tunnel" />
</div>
<x-forms.button type="submit">Continue</x-forms.button>
</form>
</x-slot:actions>
@@ -329,5 +397,4 @@
<livewire:help />
</x-modal-input>
</div>
</div>
</section>

View File

@@ -1,14 +0,0 @@
<div>
<x-slot:title>
Command Center | Coolify
</x-slot>
<h1>Command Center</h1>
<div class="subtitle">Execute commands on your servers without leaving the browser.</div>
@if ($servers->count() > 0)
<livewire:run-command :servers="$servers" />
@else
<div>
<div>No servers found. Without a server, you won't be able to do much.</div>
</div>
@endif
</div>

View File

@@ -28,16 +28,16 @@
<div class="flex flex-col justify-center flex-1">
<div class="box-title">{{ $project->name }}</div>
<div class="box-description">
{{ $project->description }}</div>
{{ $project->description }}
</div>
</div>
<div class="flex items-center justify-center gap-2 text-xs font-bold ">
<div class="flex items-center justify-center gap-2 text-xs font-bold">
<a class="hover:underline"
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<span class="p-2 font-bold">+
Add Resource</span>
href="{{ route('project.resource.create', ['project_uuid' => $project->uuid, 'environment_name' => data_get($project, 'default_environment()', 'production')]) }}">
<span class="p-2 font-bold">+ Add Resource</span>
</a>
<a class="hover:underline"
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}">
Settings
</a>
</div>
@@ -122,9 +122,19 @@
@if (count($deployments_per_server) > 0)
<x-loading />
@endif
<x-modal-confirmation isErrorButton action="cleanup_queue" buttonTitle="Cleanup Queues">
This will clean up the deployment queue. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation
title="Confirm Cleanup Queues?"
buttonTitle="Cleanup Queues"
isErrorButton
submitAction="cleanup_queue"
:actions="['All running Deployment Queues will be cleaned up.']"
:confirmWithText="false"
:confirmWithPassword="false"
step2ButtonText="Permanently Cleanup Deployment Queues"
:dispatchEvent="true"
dispatchEventType="success"
dispatchEventMessage="Deployment Queues cleanup started."
/>
</div>
<div wire:poll.3000ms="get_deployments" class="grid grid-cols-1">
@forelse ($deployments_per_server as $server_name => $deployments)
@@ -159,13 +169,17 @@
</div>
@endif
<script>
function gotoProject(uuid, environment) {
if (!environment) {
if (environment) {
window.location.href = '/project/' + uuid + '/' + environment;
} else {
window.location.href = '/project/' + uuid;
}
window.location.href = '/project/' + uuid + '/' + environment;
}
</script>
{{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}
</div>

View File

@@ -6,9 +6,10 @@
Save
</x-forms.button>
@if ($destination->network !== 'coolify')
<x-modal-confirmation isErrorButton buttonTitle="Delete Destination">
This destination will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Destination Deletion?" buttonTitle="Delete Destination" isErrorButton
submitAction="delete" :actions="['This will delete the selected destination/network.']" confirmationText="{{ $destination->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Destination Name below"
shortConfirmationLabel="Destination Name" :confirmWithPassword="false" step2ButtonText="Permanently Delete" />
@endif
</div>

View File

@@ -1,5 +1,13 @@
<div>
<x-modal-confirmation buttonFullWidth isErrorButton buttonTitle="Delete Team">
This team be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation
title="Confirm Team Deletion?"
buttonTitle="Delete Team"
isErrorButton
submitAction="delete"
:actions="['The current Team will be permanently deleted.']"
confirmationText="{{ $team }}"
confirmationLabel="Please confirm the execution of the actions by entering the Team Name below"
shortConfirmationLabel="Team Name"
step3ButtonText="Permanently Delete"
/>
</div>

View File

@@ -31,11 +31,12 @@
@endif
@forelse ($deployments as $deployment)
<div @class([
'dark:bg-coolgray-100 p-2 border-l border-dashed transition-colors hover:no-underline box-without-bg-without-border bg-white flex-col cursor-pointer dark:hover:text-neutral-400 dark:hover:bg-coolgray-200',
'border-warning' =>
'dark:bg-coolgray-100 p-2 border-l-2 transition-colors hover:no-underline box-without-bg-without-border bg-white flex-col cursor-pointer dark:hover:text-neutral-400 dark:hover:bg-coolgray-200',
'border-warning border-dashed ' =>
data_get($deployment, 'status') === 'in_progress' ||
data_get($deployment, 'status') === 'cancelled-by-user',
'border-error' => data_get($deployment, 'status') === 'failed',
'border-error border-dashed ' =>
data_get($deployment, 'status') === 'failed',
'border-success' => data_get($deployment, 'status') === 'finished',
])
x-on:click.stop="goto('{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}')">
@@ -106,7 +107,7 @@
<div class="flex flex-col" x-data="elapsedTime('{{ $deployment->deployment_uuid }}', '{{ $deployment->status }}', '{{ $deployment->created_at }}', '{{ $deployment->updated_at }}')">
<div>
@if ($deployment->status !== 'in_progress')
Finished <span x-text="measure_since_started()">0s</span> in
Finished <span x-text="measure_since_started()">0s</span> ago in
<span class="font-bold" x-text="measure_finished_time()">0s</span>
@else
Running for <span class="font-bold" x-text="measure_since_started()">0s</span>
@@ -157,7 +158,7 @@
}
},
measure_since_started() {
return dayjs.utc(created_at).fromNow();
return dayjs.utc(created_at).fromNow(true); // "true" prevents the "ago" suffix
},
}))
</script>

View File

@@ -9,6 +9,7 @@
fullscreen: false,
alwaysScroll: false,
intervalId: null,
showTimestamps: true,
makeFullscreen() {
this.fullscreen = !this.fullscreen;
if (this.fullscreen === false) {
@@ -53,63 +54,69 @@
class="dark:text-warning">{{ Str::headline(data_get($application_deployment_queue, 'status')) }}</span>.
</div>
@endif
<div id="screen" :class="fullscreen ? 'fullscreen' : ''">
<div id="screen" :class="fullscreen ? 'fullscreen' : 'relative'">
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
class="relative flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300"
:class="fullscreen ? '' : 'max-h-[40rem] border border-dotted rounded'">
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4"
x-on:click="makeFullscreen"><svg class="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="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg></button>
<button title="Go Top" x-show="fullscreen" class="fixed top-4 right-28" x-on:click="goTop"> <svg
class="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="M12 5v14m4-10l-4-4M8 9l4-4" />
</svg></button>
<button title="Follow Logs" x-show="fullscreen" :class="alwaysScroll ? 'dark:text-warning' : ''"
class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="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="M12 5v14m4-4l-4 4m-4-4l4 4" />
</svg></button>
class="flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300"
:class="fullscreen ? '' : 'min-h-14 max-h-[40rem] border border-dotted rounded'">
<div :class="fullscreen ? 'fixed' : 'absolute'" class="top-4 right-6">
<div class="flex justify-end gap-4 fixed -translate-x-full">
<button title="Toggle timestamps" x-on:click="showTimestamps = !showTimestamps">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none"
stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
</button>
<button title="Go Top" x-show="fullscreen" x-on:click="goTop">
<svg class="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="M12 5v14m4-10l-4-4M8 9l4-4" />
</svg>
</button>
<button title="Follow Logs" x-show="fullscreen" :class="alwaysScroll ? 'dark:text-warning' : ''"
x-on:click="toggleScroll">
<svg class="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="M12 5v14m4-4l-4 4m-4-4l4 4" />
</svg>
</button>
<button title="Fullscreen" x-show="!fullscreen" x-on:click="makeFullscreen">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none">
<path
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
<path fill="currentColor"
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
</g>
</svg>
</button>
<button title="Minimize" x-show="fullscreen" x-on:click="makeFullscreen">
<svg class="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="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg>
</button>
</div>
</div>
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-8"
x-on:click="makeFullscreen"><svg class="fixed icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none">
<path
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
<path fill="currentColor"
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
</g>
</svg></button>
<div id="logs" class="flex flex-col font-mono">
@if (decode_remote_command_output($application_deployment_queue)->count() > 0)
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
@forelse ($this->logLines as $line)
<div @class([
'mt-2' => $line['command'] ?? false,
'flex gap-2 dark:hover:bg-coolgray-500 hover:bg-gray-100',
])>
<span x-show="showTimestamps" class="shrink-0 text-gray-500">{{ $line['timestamp'] }}</span>
<span @class([
'text-coollabs dark:text-warning whitespace-pre-line' => $line['hidden'],
'text-red-500 font-bold whitespace-pre-line' => $line['type'] == 'stderr',
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
<br><br>[COMMAND] {{ $line['command'] }}<br>[OUTPUT]
@endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://'))
@php
$line['output'] = preg_replace(
'/(https?:\/\/[^\s]+)/',
'<a href="$1" target="_blank" class="underline text-neutral-400">$1</a>',
$line['output'],
);
@endphp {!! $line['output'] !!}
@else
{{ $line['output'] }}
@endif
</span>
@endforeach
@else
<span class="font-mono text-neutral-400">No logs yet.</span>
@endif
'text-coollabs dark:text-warning' => $line['hidden'],
'text-red-500' => $line['stderr'],
'font-bold' => $line['command'] ?? false,
'whitespace-pre-wrap',
])>{!! $line['line'] !!}</span>
</div>
@empty
<span class="font-mono text-neutral-400 mb-2">No logs yet.</span>
@endforelse
</div>
</div>
</div>

View File

@@ -68,11 +68,13 @@
<option value="www">Redirect to www.</option>
<option value="non-www">Redirect to non-www.</option>
</x-forms.select>
<x-modal-confirmation action="set_redirect">
<x-modal-confirmation title="Confirm Redirection Setting?" buttonTitle="Set Direction"
submitAction="set_redirect" :actions="['All traffic will be redirected to the selected direction.']" confirmationText="{{ $application->fqdn . '/' }}"
confirmationLabel="Please confirm the execution of the action by entering the Application URL below"
shortConfirmationLabel="Application URL" :confirmWithPassword="false" step2ButtonText="Set Direction">
<x-slot:customButton>
<div class="w-[7.2rem]">Set Direction</div>
</x-slot:customButton>
This will reset the container labels. Are you sure?
</x-modal-confirmation>
</div>
@endif
@@ -254,6 +256,11 @@
helper="You need to modify the docker compose file." monacoEditorLanguage="yaml"
useMonacoEditor />
@else
@if ($application->compose_parsing_version === '3')
<x-forms.textarea rows="10" readonly id="application.docker_compose_raw"
label="Docker Compose Content (raw)" helper="You need to modify the docker compose file."
monacoEditorLanguage="yaml" useMonacoEditor />
@endif
<x-forms.textarea rows="10" readonly id="application.docker_compose"
label="Docker Compose Content" helper="You need to modify the docker compose file."
monacoEditorLanguage="yaml" useMonacoEditor />
@@ -297,12 +304,15 @@
helper="If you know what are you doing, you can enable this to edit the labels directly. Coolify won't update labels automatically. <br><br>Be careful, it could break the proxy configuration after you restart the container."
id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
</div>
<x-modal-confirmation buttonFullWidth action="resetDefaultLabels"
buttonTitle="Reset to Coolify Generated Labels">
Are you sure you want to reset the labels to Coolify generated labels? <br>It could break the proxy
configuration after you restart the container.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Labels Reset to Coolify Defaults?"
buttonTitle="Reset Labels to Coolify Defaults" buttonFullWidth submitAction="resetDefaultLabels"
:actions="[
'All your custom proxy labels will be lost.',
'Proxy labels (traefik, caddy, etc) will be reset to the coolify defaults.',
]" confirmationText="{{ $application->fqdn . '/' }}"
confirmationLabel="Please confirm the execution of the actions by entering the Application URL below"
shortConfirmationLabel="Application URL" :confirmWithPassword="false"
step2ButtonText="Permanently Reset Labels" />
@endif
<h3 class="pt-8">Pre/Post Deployment Commands</h3>

View File

@@ -1,7 +1,7 @@
<nav wire:poll.10000ms="check_status">
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :lastDeploymentInfo="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
<div class="navbar-main">
<nav class="flex items-center flex-shrink-0 gap-6 scrollbar min-h-10 whitespace-nowrap">
<nav class="flex flex-shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
<a href="{{ route('project.application.configuration', $parameters) }}">
Configuration
</a>
@@ -13,12 +13,12 @@
</a>
@if (!$application->destination->server->isSwarm())
<a href="{{ route('project.application.command', $parameters) }}">
<button>Command</button>
<button>Terminal</button>
</a>
@endif
<x-applications.links :application="$application" />
</nav>
<div class="flex flex-wrap items-center gap-2">
<div class="flex flex-wrap gap-2 items-center">
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
<div>Please load a Compose file.</div>
@else
@@ -72,7 +72,13 @@
</x-forms.button>
@endif
@endif
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
submitAction="stop" :checkboxes="$checkboxes" :actions="[
'This application will be stopped.',
'All non-persistent data of this application will be deleted.',
]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Confirm" :dispatchEvent="true"
dispatchEventType="stopEvent">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -87,7 +93,6 @@
</svg>
Stop
</x-slot:button-title>
This application will be stopped. <br>Please think again.
</x-modal-confirmation>
@else
<x-forms.button wire:click='deploy'>

View File

@@ -152,8 +152,15 @@
@endif
</x-forms.button>
@if (data_get($preview, 'status') !== 'exited')
<x-modal-confirmation isErrorButton
action="stop({{ data_get($preview, 'pull_request_id') }})">
<x-modal-confirmation
title="Confirm Preview Deployment Stopping?"
buttonTitle="Stop"
submitAction="stop({{ data_get($preview, 'pull_request_id') }})"
:actions="['This preview deployment will be stopped.', 'If the preview deployment is currently in use data could be lost.', 'All non-persistent data of this preview deployment (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the preview deployment again).']"
:confirmWithText="false"
:confirmWithPassword="false"
step2ButtonText="Stop Preview Deployment"
>
<x-slot:customButton>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
@@ -168,14 +175,19 @@
</svg>
Stop
</x-slot:customButton>
This will stop the preview deployment. <br>Please think again.
</x-modal-confirmation>
@endif
<x-modal-confirmation isErrorButton
action="delete({{ data_get($preview, 'pull_request_id') }})" buttonTitle="Delete">
This will delete the preview deployment. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation
title="Confirm Preview Deployment Deletion?"
buttonTitle="Delete"
isErrorButton
submitAction="delete({{ data_get($preview, 'pull_request_id') }})"
:actions="['All containers of this preview deployment will be stopped and permanently deleted.']"
confirmationText="{{ data_get($preview, 'fqdn'). '/' }}"
confirmationLabel="Please confirm the execution of the actions by entering the Preview Deployment name below"
shortConfirmationLabel="Preview Deployment Name"
:confirmWithPassword="false"
/>
</div>
</div>
@endforeach

View File

@@ -8,12 +8,17 @@
<livewire:project.database.backup-now :backup="$backup" />
@endif
@if ($backup->database_id !== 0)
<x-modal-confirmation isErrorButton>
<x-slot:button-title>
Delete
</x-slot:button-title>
This will stop the scheduled backup for this database.<br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation
title="Confirm Backup Schedule Deletion?"
buttonTitle="Delete Backups and Schedule"
isErrorButton
submitAction="delete"
:checkboxes="$checkboxes"
:actions="['The selected backup schedule will be deleted.', 'Scheduled backups for this database will be stopped (if this is the only backup schedule for this database).']"
confirmationText="{{ $backup->database->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Database Name of the scheduled backups below"
shortConfirmationLabel="Database Name"
/>
@endif
</div>
<div class="w-48 pb-2">

View File

@@ -4,47 +4,65 @@
<h3 class="py-4">Executions</h3>
<x-forms.button wire:click='cleanupFailed'>Cleanup Failed Backups</x-forms.button>
</div>
<div class="flex flex-col-reverse gap-2">
<div class="flex flex-col gap-4">
@forelse($executions as $execution)
<form wire:key="{{ data_get($execution, 'id') }}"
class="relative flex flex-col p-4 bg-white box-without-bg dark:bg-coolgray-100"
@class([
'border-green-500' => data_get($execution, 'status') === 'success',
'border-red-500' => data_get($execution, 'status') === 'failed',
])>
<div wire:key="{{ data_get($execution, 'id') }}" @class([
'flex flex-col border-l-2 transition-colors p-4 ',
'bg-white dark:bg-coolgray-100 ',
'text-black dark:text-white',
'border-green-500' => data_get($execution, 'status') === 'success',
'border-red-500' => data_get($execution, 'status') === 'failed',
'border-yellow-500' => data_get($execution, 'status') === 'running',
])>
@if (data_get($execution, 'status') === 'running')
<div class="absolute top-2 right-2">
<x-loading />
</div>
@endif
<div>Database: {{ data_get($execution, 'database_name', 'N/A') }}</div>
<div>Status: {{ data_get($execution, 'status') }}</div>
<div>Started At: {{ data_get($execution, 'created_at') }}</div>
@if (data_get($execution, 'message'))
<div>Message: {{ data_get($execution, 'message') }}</div>
@endif
<div>Size: {{ data_get($execution, 'size') }} B /
{{ round((int) data_get($execution, 'size') / 1024, 2) }}
kB / {{ round((int) data_get($execution, 'size') / 1024 / 1024, 3) }} MB
<div class="text-gray-700 dark:text-gray-300 font-semibold mb-1">Status:
{{ data_get($execution, 'status') }}</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Started At: {{ $this->formatDateInServerTimezone(data_get($execution, 'created_at')) }}
</div>
<div>Location: {{ data_get($execution, 'filename', 'N/A') }}</div>
<div class="flex gap-2">
<div class="flex-1"></div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Database: {{ data_get($execution, 'database_name', 'N/A') }}
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Size: {{ data_get($execution, 'size') }} B /
{{ round((int) data_get($execution, 'size') / 1024, 2) }} kB /
{{ round((int) data_get($execution, 'size') / 1024 / 1024, 3) }} MB
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Location: {{ data_get($execution, 'filename', 'N/A') }}
</div>
@if (data_get($execution, 'message'))
<div class="mt-2 p-2 bg-gray-100 dark:bg-coolgray-200 rounded">
<pre class="whitespace-pre-wrap text-sm">{{ data_get($execution, 'message') }}</pre>
</div>
@endif
<div class="flex gap-2 mt-4">
@if (data_get($execution, 'status') === 'success')
<x-forms.button class=" dark:hover:bg-coolgray-400"
<x-forms.button class="dark:hover:bg-coolgray-400"
x-on:click="download_file('{{ data_get($execution, 'id') }}')">Download</x-forms.button>
@endif
<x-modal-confirmation isErrorButton action="deleteBackup({{ data_get($execution, 'id') }})">
<x-slot:button-title>
Delete
</x-slot:button-title>
This will delete this backup. It is not reversible.<br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation
title="Confirm Backup Deletion?"
buttonTitle="Delete"
isErrorButton
submitAction="deleteBackup({{ data_get($execution, 'id') }})"
{{-- :checkboxes="$checkboxes" --}}
:actions="[
'This backup will be permanently deleted from local storage.'
]"
confirmationText="{{ data_get($execution, 'filename') }}"
confirmationLabel="Please confirm the execution of the actions by entering the Backup Filename below"
shortConfirmationLabel="Backup Filename"
step3ButtonText="Permanently Delete"
/>
</div>
</form>
</div>
@empty
<div>No executions found.</div>
<div class="p-4 bg-gray-100 dark:bg-coolgray-100 rounded">No executions found.</div>
@endforelse
</div>
<script>
@@ -53,5 +71,4 @@
}
</script>
@endisset
</div>

View File

@@ -21,14 +21,18 @@
readonly helper="You can only change this in the database." />
</div>
@else
<div class="pt-8 dark:text-warning">Please verify these values. You can only modify them before the initial
<div class=" dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2 pb-8">
<div class="flex gap-2">
<x-forms.input label="Username" id="database.clickhouse_admin_user" required />
<x-forms.input label="Password" id="database.clickhouse_admin_password" type="password" required />
</div>
@endif
<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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">

View File

@@ -11,6 +11,10 @@
<x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required />
</div>
<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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">

View File

@@ -8,19 +8,20 @@
</x-slide-over>
<div class="navbar-main">
<nav
class="flex items-center flex-shrink-0 gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
class="flex overflow-x-scroll flex-shrink-0 gap-6 items-center whitespace-nowrap sm:overflow-x-hidden scrollbar min-h-10">
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.configuration', $parameters) }}">
<button>Configuration</button>
</a>
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.command', $parameters) }}">
<button>Execute Command</button>
</a>
<a class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.logs', $parameters) }}">
<button>Logs</button>
</a>
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.command', $parameters) }}">
<button>Terminal</button>
</a>
@if (
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
@@ -32,9 +33,14 @@
</a>
@endif
</nav>
<div class="flex flex-wrap items-center gap-2">
<div class="flex flex-wrap gap-2 items-center">
@if (!str($database->status)->startsWith('exited'))
<x-modal-confirmation @click="$wire.dispatch('restartEvent')">
<x-modal-confirmation title="Confirm Database Restart?" buttonTitle="Restart" submitAction="restart"
:actions="[
'This database will be unavailable during the restart.',
'If the database is currently in use data could be lost.',
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Database"
:dispatchEvent="true" dispatchEventType="restartEvent">
<x-slot:button-title>
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
@@ -45,9 +51,14 @@
</svg>
Restart
</x-slot:button-title>
This database will be restarted. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
<x-modal-confirmation title="Confirm Database Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[
'This database will be stopped.',
'If the database is currently in use data could be lost.',
'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).',
]" :confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue"
step2ButtonText="Stop Database" :dispatchEvent="true" dispatchEventType="stopEvent">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -60,7 +71,6 @@
</svg>
Stop
</x-slot:button-title>
This database will be stopped. <br>Please think again.
</x-modal-confirmation>
@else
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">

View File

@@ -2,9 +2,13 @@
<div class="flex items-end gap-2">
<x-forms.input id="filename" label="Filename" />
<x-forms.button type="submit">Save</x-forms.button>
<x-modal-confirmation isErrorButton buttonTitle="Delete">
This script will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm init-script deletion?" buttonTitle="Delete" isErrorButton
submitAction="delete" :actions="[
'The init-script of this database will be permanently deleted.',
'If you are actively using this init-script, it could cause errors on redeployment.',
]" confirmationText="{{ $filename }}"
confirmationLabel="Please confirm the execution of the actions by entering the init-script name below"
shortConfirmationLabel="Init-script Name" :confirmWithPassword=false step2ButtonText="Permanently Delete" />
</div>
<x-forms.textarea id="content" label="Content" />
</form>

View File

@@ -12,6 +12,10 @@
<x-forms.input label="Image" id="database.image" required
helper="For all available images, check here:<br><br><a target='_blank' href=https://hub.docker.com/r/eqalpha/keydb'>https://hub.docker.com/r/eqalpha/keydb</a>" />
</div>
<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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">

View File

@@ -16,30 +16,40 @@
automations (like backups) won't work.
</div>
@if ($database->started_at)
<div class="flex flex-col gap-2">
<div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User" id="database.mariadb_user" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mariadb_database"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
</div>
@else
<div class="flex flex-col gap-2 pb-2">
<div class="flex xl:flex-row flex-col gap-2 pb-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password"
helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mariadb_user" required
helper="You can only change this in the database." />
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
helper="You can only change this in the database." />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mariadb_database"
placeholder="If empty, it will be the same as Username."
helper="You can only change this in the database." />
</div>
@endif
<div class="pt-2">
<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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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="database.custom_docker_run_options" label="Custom Docker Options" />
</div>
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">

View File

@@ -16,7 +16,7 @@
automations (like backups) won't work.
</div>
@if ($database->started_at)
<div class="flex flex-col gap-2">
<div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Initial Username" id="database.mongo_initdb_root_username"
placeholder="If empty: postgres"
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
@@ -28,7 +28,7 @@
helper="You can only change this in the database." />
</div>
@else
<div class="flex flex-col gap-2 pb-2">
<div class="flex xl:flex-row flex-col gap-2 pb-2">
<x-forms.input required label="Username" id="database.mongo_initdb_root_username"
placeholder="If empty: postgres" />
<x-forms.input label="Password" id="database.mongo_initdb_root_password" type="password" required />
@@ -36,6 +36,10 @@
placeholder="If empty, it will be the same as Username." />
</div>
@endif
<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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">

View File

@@ -16,30 +16,40 @@
automations (like backups) won't work.
</div>
@if ($database->started_at)
<div class="flex flex-col gap-2">
<div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User" id="database.mysql_user" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mysql_database"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
</div>
@else
<div class="flex flex-col gap-4 pb-2">
<div class="flex xl:flex-row flex-col gap-4 pb-2">
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password"
helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mysql_user" required
helper="You can only change this in the database." />
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
helper="You can only change this in the database." />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mysql_database"
placeholder="If empty, it will be the same as Username."
helper="You can only change this in the database." />
</div>
@endif
<div class="pt-2">
<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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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="database.custom_docker_run_options" label="Custom Docker Options" />
</div>
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">

View File

@@ -30,7 +30,7 @@
automations (like backups) won't work.
</div>
@if ($database->started_at)
<div class="flex flex-col gap-2">
<div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres"
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Password" id="database.postgres_password" type="password" required
@@ -40,7 +40,7 @@
helper="You can only change this in the database." />
</div>
@else
<div class="flex flex-col gap-2 pb-2">
<div class="flex xl:flex-row flex-col gap-2 pb-2">
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres" />
<x-forms.input label="Password" id="database.postgres_password" type="password" required />
<x-forms.input label="Initial Database" id="database.postgres_db"
@@ -53,6 +53,10 @@
<x-forms.input label="Host Auth Method" id="database.postgres_host_auth_method"
placeholder="If empty, use default. See in docker docs." />
</div>
<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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">

View File

@@ -20,6 +20,10 @@
@endif
<x-forms.input label="Password" id="database.redis_password" type="password" required helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." wire:model.defer="database.redis_password" />
</div>
<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='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>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="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">

View File

@@ -1,38 +1,37 @@
<div>
<div class="flex flex-col gap-2">
@forelse($database->scheduledBackups as $backup)
@if ($type == 'database')
<a class="box"
href="{{ route('project.database.backup.execution', [...$parameters, 'backup_uuid' => $backup->uuid]) }}">
<div class="flex flex-col">
<div>Frequency: {{ $backup->frequency }}</div>
<div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div>
<div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div>
</div>
</a>
@else
<div class="box" wire:click="setSelectedBackup('{{ data_get($backup, 'id') }}')">
<div @class([
'border-coollabs' =>
data_get($backup, 'id') === data_get($selectedBackup, 'id'),
'flex flex-col border-l-2 border-transparent',
])>
<div>Frequency: {{ $backup->frequency }}</div>
<div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div>
<div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div>
</div>
</div>
@endif
@if ($type == 'database')
<a class="box"
href="{{ route('project.database.backup.execution', [...$parameters, 'backup_uuid' => $backup->uuid]) }}">
<div class="flex flex-col">
<div>Frequency: {{ $backup->frequency }}</div>
<div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div>
<div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div>
</div>
</a>
@else
<div class="box" wire:click="setSelectedBackup('{{ data_get($backup, 'id') }}')">
<div @class([ 'border-coollabs'=>
data_get($backup, 'id') === data_get($selectedBackup, 'id'),
'flex flex-col border-l-2 border-transparent',
])>
<div>Frequency: {{ $backup->frequency }}</div>
<div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div>
<div>Number of backups to keep (locally): {{ $backup->number_of_backups_locally }}</div>
</div>
</div>
@endif
@empty
<div>No scheduled backups configured.</div>
<div>No scheduled backups configured.</div>
@endforelse
</div>
@if ($type === 'service-database' && $selectedBackup)
<div class="pt-10">
<livewire:project.database.backup-edit wire:key="{{ $selectedBackup->id }}" :backup="$selectedBackup"
:s3s="$s3s" :status="data_get($database, 'status')" />
<h3 class="py-4">Executions</h3>
<livewire:project.database.backup-executions wire:key="{{ $selectedBackup->id }}" :backup="$selectedBackup" />
</div>
<div class="pt-10">
<livewire:project.database.backup-edit wire:key="{{ $selectedBackup->id }}" :backup="$selectedBackup"
:s3s="$s3s" :status="data_get($database, 'status')" />
<h3 class="py-4">Executions</h3>
<livewire:project.database.backup-executions wire:key="{{ $selectedBackup->id }}" :backup="$selectedBackup" :database="$database" />
</div>
@endif
</div>

View File

@@ -1,3 +1,5 @@
<x-modal-confirmation isErrorButton buttonTitle="Delete Environment" disabled="{{ $disabled }}">
This environment will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Environment Deletion?" buttonTitle="Delete Environment" isErrorButton
submitAction="delete" :actions="['This will delete the selected environment.']"
confirmationLabel="Please confirm the execution of the actions by entering the Environment Name below"
shortConfirmationLabel="Environment Name" confirmationText="{{ $environmentName }}" :confirmWithPassword="false"
step2ButtonText="Permanently Delete" />

View File

@@ -1,3 +1,7 @@
<x-modal-confirmation isErrorButton buttonTitle="Delete Project" disabled="{{ $disabled }}">
This project will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Project Deletion?" buttonTitle="Delete Project" isErrorButton submitAction="delete"
:actions="[
'This will delete the selected project',
'All Environments inside the project will be deleted as well.',
]" confirmationLabel="Please confirm the execution of the actions by entering the Project Name below"
shortConfirmationLabel="Project Name" confirmationText="{{ $projectName }}" :confirmWithPassword="false"
step2ButtonText="Permanently Delete" />

View File

@@ -11,9 +11,8 @@
<div class="subtitle">All your projects are here.</div>
<div class="grid gap-2 lg:grid-cols-2">
@forelse ($projects as $project)
<div class="box group" x-data x-on:click="goto('{{ $project->uuid }}')">
<div class="flex flex-col justify-center flex-1 mx-6"
onclick="gotoProject('{{ $project->uuid }}','{{ $project->default_environment() }}')">
<div class="box group" onclick="gotoProject('{{ $project->uuid }}', '{{ $project->default_environment() }}')">
<div class="flex flex-col justify-center flex-1 mx-6">
<div class="box-title">{{ $project->name }}</div>
<div class="box-description ">
{{ $project->description }}</div>
@@ -33,15 +32,12 @@
</div>
<script>
function goto(uuid) {
function gotoProject(uuid, environment) {
if (environment) {
window.location.href = '/project/' + uuid + '/' + environment;
} else {
window.location.href = '/project/' + uuid;
}
function gotoProject(uuid, environment) {
if (!environment) {
window.location.href = '/project/' + uuid;
}
window.location.href = '/project/' + uuid + '/' + environment;
}
</script>
}
</script>
</div>

View File

@@ -4,7 +4,7 @@
<form class="flex flex-col gap-2" wire:submit='loadBranch'>
<div class="flex flex-col gap-2">
<div class="flex flex-col gap-2">
<div class="flex items-end gap-2">
<div class="flex gap-2 items-end">
<x-forms.input required id="repository_url" label="Repository URL (https://)"
helper="{!! __('repository.url') !!}" />
<x-forms.button type="submit">
@@ -47,7 +47,7 @@
@if ($build_pack === 'dockercompose')
<x-forms.input placeholder="/" wire:model.blur="base_directory" label="Base Directory"
helper="Directory to use as root. Useful for monorepos." />
<x-forms.input placeholder="/docker-compose.yaml" id="docker_compose_location"
<x-forms.input placeholder="/docker-compose.yaml" wire:model.blur="docker_compose_location"
label="Docker Compose Location"
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($base_directory . $docker_compose_location, '/') }}</span>" />
Compose file location in your repository:<span

View File

@@ -491,7 +491,9 @@
</div>
<div class="pb-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.</div>
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">here</a>.</div>
<input class="input" autofocus wire:model.live.debounce.200ms="search" autofocus
placeholder="Search...">
@if ($loadingServices)
@@ -617,6 +619,85 @@
@endif
</div>
@endif
@if ($current_step === 'select-postgresql-type')
<h2>Select a Postgresql type</h2>
<div>If you need extra extensions, you can select Supabase PostgreSQL (or others), otherwise select PostgreSQL
16 (default).</div>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
wire:click="setPostgresqlType('postgres:16-alpine')">
<div class="flex flex-col">
<div class="box-title">PostgreSQL 16 (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 group-hover:dark: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 cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
wire:click="setPostgresqlType('supabase/postgres:15.6.1.113')">
<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 group-hover:dark: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 cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
wire:click="setPostgresqlType('postgis/postgis')">
<div class="flex flex-col">
<div class="box-title">PostGIS</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 group-hover:dark: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 cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
wire:click="setPostgresqlType('pgvector/pgvector:pg16')">
<div class="flex flex-col">
<div class="box-title">PGVector (16)</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 group-hover:dark: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"

View File

@@ -3,8 +3,8 @@
{{ data_get_str($service, 'name')->limit(10) }} > Configuration | Coolify
</x-slot>
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
<div class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
<div class="flex flex-col items-start gap-2 min-w-fit">
<div class="flex flex-col gap-8 pt-6 h-full sm:flex-row">
<div class="flex flex-col gap-2 items-start min-w-fit">
<a class="menu-item sm:min-w-fit" target="_blank" href="{{ $service->documentation() }}">Documentation
<x-external-link /></a>
<a class="menu-item sm:min-w-fit" :class="activeTab === 'service-stack' && 'menu-item-active'"
@@ -23,10 +23,6 @@
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
href="#">Scheduled Tasks
</a>
<a class="menu-item sm:min-w-fit" :class="activeTab === 'execute-command' && 'menu-item-active'"
@click.prevent="activeTab = 'execute-command';
window.location.hash = 'execute-command'"
href="#">Execute Command</a>
<a class="menu-item sm:min-w-fit" :class="activeTab === 'logs' && 'menu-item-active'"
@click.prevent="activeTab = 'logs';
window.location.hash = 'logs'"
@@ -80,7 +76,7 @@
@endif
@if ($application->fqdn)
<span class="flex gap-1 text-xs">{{ Str::limit($application->fqdn, 60) }}
<x-modal-input title="Edit Domains">
<x-modal-input title="Edit Domains" :closeOutside="false">
<x-slot:content>
<span class="cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg"
@@ -111,11 +107,14 @@
Settings
</a>
@if (str($application->status)->contains('running'))
<x-modal-confirmation action="restartApplication({{ $application->id }})"
isErrorButton buttonTitle="Restart">
This application will be unavailable during the restart. <br>Please think
again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Service Application Restart?"
buttonTitle="Restart"
submitAction="restartApplication({{ $application->id }})" :actions="[
'The selected service application will be unavailable during the restart.',
'If the service application is currently in use data could be lost.',
]"
:confirmWithText="false" :confirmWithPassword="false"
step2ButtonText="Restart Service Container" />
@endif
</div>
</div>
@@ -155,11 +154,13 @@
Settings
</a>
@if (str($database->status)->contains('running'))
<x-modal-confirmation action="restartDatabase({{ $database->id }})"
isErrorButton buttonTitle="Restart">
This database will be unavailable during the restart. <br>Please think
again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Service Database Restart?"
buttonTitle="Restart" submitAction="restartDatabase({{ $database->id }})"
:actions="[
'This service database will be unavailable during the restart.',
'If the service database is currently in use data could be lost.',
]" :confirmWithText="false" :confirmWithPassword="false"
step2ButtonText="Restart Database" />
@endif
</div>
</div>
@@ -168,7 +169,7 @@
</div>
</div>
<div x-cloak x-show="activeTab === 'storages'">
<div class="flex items-center gap-2">
<div class="flex gap-2 items-center">
<h2>Storages</h2>
</div>
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
@@ -179,7 +180,8 @@
lazy />
@endforeach
@foreach ($databases as $database)
<livewire:project.service.storage wire:key="database-{{ $database->id }}" :resource="$database" lazy />
<livewire:project.service.storage wire:key="database-{{ $database->id }}" :resource="$database"
lazy />
@endforeach
</div>
<div x-cloak x-show="activeTab === 'scheduled-tasks'">
@@ -191,9 +193,6 @@
<div x-cloak x-show="activeTab === 'logs'">
<livewire:project.shared.logs :resource="$service" />
</div>
<div x-cloak x-show="activeTab === 'execute-command'">
<livewire:project.shared.execute-container-command :resource="$service" />
</div>
<div x-cloak x-show="activeTab === 'environment-variables'">
<livewire:project.shared.environment-variable.all :resource="$service" />
</div>

View File

@@ -1,49 +1,79 @@
<div class="p-4 transition border rounded dark:border-coolgray-200">
<div class="py-4 ">
<div class="flex flex-col justify-center pb-4 text-sm select-text">
@if (data_get($resource, 'build_pack') === 'dockercompose')
<h2>{{ data_get($resource, 'name', 'unknown') }}</h2>
@endif
{{-- @if (data_get($resource, 'build_pack') === 'dockercompose')
<h4>{{ data_get($resource, 'name', 'unknown') }}</h4>
@endif --}}
@if ($fileStorage->is_directory)
<div class="dark:text-white">Directory Mount</div>
<h4 class="dark:text-white pt-4 border-t dark:border-coolgray-200">Directory Mount</h4>
@else
<div class="dark:text-white">File Mount</div>
<h4 class="dark:text-white pt-4 border-t dark:border-coolgray-200">File Mount</h4>
@endif
<div>{{ $workdir }}{{ $fs_path }} -> {{ $fileStorage->mount_path }}</div>
<x-forms.input label="Source Path" :value="$fileStorage->fs_path" readonly />
<x-forms.input label="Destination Path" :value="$fileStorage->mount_path" readonly />
</div>
<form wire:submit='submit' class="flex flex-col gap-2">
<div class="flex gap-2">
@if ($fileStorage->is_directory)
<x-modal-confirmation action="convertToFile" buttonTitle="Convert to file">
<div>This will delete all files in this directory. It is not reversible. <strong
class="text-error">Please think
again.</strong><br><br></div>
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Directory Conversion to File?" buttonTitle="Convert to file"
submitAction="convertToFile" :actions="[
'All files in this directory will be permanently deleted and an empty file will be created in its place.',
]" confirmationText="{{ $fs_path }}"
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
shortConfirmationLabel="Filepath" :confirmWithPassword="false" step2ButtonText="Convert to file" />
@else
<x-modal-confirmation action="convertToDirectory" buttonTitle="Convert to directory">
<div>This will delete the file and make a directory instead. It is not reversible.
<strong class="text-error">Please think
again.</strong><br><br>
</div>
<x-modal-confirmation title="Confirm File Conversion to Directory?" buttonTitle="Convert to directory"
submitAction="convertToDirectory" :actions="[
'The selected file will be permanently deleted and an empty directory will be created in its place.',
]" confirmationText="{{ $fs_path }}"
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
shortConfirmationLabel="Filepath" :confirmWithPassword="false" step2ButtonText="Convert to directory" />
@endif
@if ($fileStorage->is_directory)
<x-modal-confirmation title="Confirm Directory Deletion?" buttonTitle="Delete Directory" isErrorButton
submitAction="delete" :checkboxes="$directoryDeletionCheckboxes" :actions="[
'The selected directory and all its contents will be permanently deleted from the container.',
]" confirmationText="{{ $fs_path }}"
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
shortConfirmationLabel="Filepath" step3ButtonText="Permanently Delete" />
@else
<x-modal-confirmation title="Confirm File Deletion?" buttonTitle="Delete File" isErrorButton
submitAction="delete" :checkboxes="$fileDeletionCheckboxes" :actions="['The selected file will be permanently deleted from the container.']" confirmationText="{{ $fs_path }}"
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
shortConfirmationLabel="Filepath" step3ButtonText="Permanently Delete" />
@endif
@if (!$fileStorage->is_based_on_git)
<x-modal-confirmation isErrorButton buttonTitle="Delete">
<div class="px-2">This storage will be deleted. It is not reversible. <strong
class="text-error">Please
think
again.</strong><br><br></div>
<h4>Actions</h4>
@if ($fileStorage->is_directory)
<x-forms.checkbox id="permanently_delete"
label="Permanently delete directory from the server?"></x-forms.checkbox>
@else
<x-forms.checkbox id="permanently_delete"
label="Permanently delete file from the server?"></x-forms.checkbox>
@endif
</x-modal-confirmation>
@endif
<x-modal-confirmation isErrorButton buttonTitle="Delete">
<div class="px-2">This storage will be deleted. It is not reversible. <strong
class="text-error">Please
think
again.</strong><br><br></div>
<h4>Actions</h4>
@if ($fileStorage->is_directory)
<x-forms.checkbox id="permanently_delete"
label="Permanently delete directory from the server?"></x-forms.checkbox>
@else
<x-forms.checkbox id="permanently_delete"
label="Permanently delete file from the server?"></x-forms.checkbox>
@endif
</x-modal-confirmation>
</div>
@if (!$fileStorage->is_directory)
<x-forms.textarea label="Content" rows="20" id="fileStorage.content"></x-forms.textarea>
<x-forms.button class="w-full" type="submit">Save</x-forms.button>
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
<div class="w-96">
<x-forms.checkbox instantSave label="Is this based on the Git repository?"
id="fileStorage.is_based_on_git"></x-forms.checkbox>
</div>
@endif
<x-forms.textarea
label="{{ $fileStorage->is_based_on_git ? 'Content (refreshed after a successful deployment)' : 'Content' }}"
rows="20" id="fileStorage.content"
readonly="{{ $fileStorage->is_based_on_git }}"></x-forms.textarea>
@if (!$fileStorage->is_based_on_git)
<x-forms.button class="w-full" type="submit">Save</x-forms.button>
@endif
@endif
</form>

View File

@@ -6,17 +6,21 @@
<livewire:activity-monitor header="Logs" showWaiting fullHeight />
</x-slot:content>
</x-slide-over>
<h1>Configuration</h1>
<h1>{{ $title }}</h1>
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
<div class="navbar-main" x-data>
<nav class="flex items-center flex-shrink-0 gap-6 scrollbar min-h-10 whitespace-nowrap">
<nav class="flex flex-shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
href="{{ route('project.service.configuration', $parameters) }}">
<button>Configuration</button>
</a>
<a class="{{ request()->routeIs('project.service.command') ? 'dark:text-white' : '' }}"
href="{{ route('project.service.command', $parameters) }}">
<button>Terminal</button>
</a>
<x-services.links :service="$service" />
</nav>
<div class="flex flex-wrap items-center order-first gap-2 sm:order-last">
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
@if (str($service->status())->contains('running'))
<button @click="$wire.dispatch('restartEvent')" class="gap-2 button">
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
@@ -28,7 +32,9 @@
</svg>
Pull Latest Images & Restart
</button>
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue"
step2ButtonText="Stop Service" :dispatchEvent="true" dispatchEventType="stopEvent">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -41,7 +47,6 @@
</svg>
Stop
</x-slot:button-title>
This service will be stopped. <br>Please think again.
</x-modal-confirmation>
@elseif (str($service->status())->contains('degraded'))
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
@@ -54,7 +59,10 @@
</svg>
Restart Degraded Services
</button>
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Stop Service" :dispatchEvent="true"
dispatchEventType="stopEvent">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -67,11 +75,10 @@
</svg>
Stop
</x-slot:button-title>
This service will be stopped. <br>Please think again.
</x-modal-confirmation>
@elseif (str($service->status())->contains('exited'))
<button wire:click='stop(true)' class="gap-2 button">
<svg class="w-5 h-5 " viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<svg class="w-5 h-5" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path fill="red" d="M26 20h-6v-2h6zm4 8h-6v-2h6zm-2-4h-6v-2h6z" />
<path fill="red"
d="M17.003 20a4.895 4.895 0 0 0-2.404-4.173L22 3l-1.73-1l-7.577 13.126a5.699 5.699 0 0 0-5.243 1.503C3.706 20.24 3.996 28.682 4.01 29.04a1 1 0 0 0 1 .96h14.991a1 1 0 0 0 .6-1.8c-3.54-2.656-3.598-8.146-3.598-8.2Zm-5.073-3.003A3.11 3.11 0 0 1 15.004 20c0 .038.002.208.017.469l-5.9-2.624a3.8 3.8 0 0 1 2.809-.848ZM15.45 28A5.2 5.2 0 0 1 14 25h-2a6.5 6.5 0 0 0 .968 3h-2.223A16.617 16.617 0 0 1 10 24H8a17.342 17.342 0 0 0 .665 4H6c.031-1.836.29-5.892 1.803-8.553l7.533 3.35A13.025 13.025 0 0 0 17.596 28Z" />
@@ -88,7 +95,10 @@
Deploy
</button>
@else
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Stop Service" :dispatchEvent="true"
dispatchEventType="stopEvent">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -101,7 +111,6 @@
</svg>
Stop
</x-slot:button-title>
This service will be stopped. <br>Please think again.
</x-modal-confirmation>
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"

View File

@@ -7,12 +7,11 @@
<h2>{{ Str::headline($application->name) }}</h2>
@endif
<x-forms.button type="submit">Save</x-forms.button>
<x-modal-confirmation isErrorButton>
<x-slot:button-title>
Delete
</x-slot:button-title>
This will delete this service application. It is not reversible.<br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Service Application Deletion?" buttonTitle="Delete" isErrorButton
submitAction="delete" {{-- :checkboxes="$checkboxes" --}} :actions="['The selected service application container will be stopped and permanently deleted.']"
confirmationText="{{ Str::headline($application->name) }}"
confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below"
shortConfirmationLabel="Service Application Name" step3ButtonText="Permanently Delete" />
</div>
<div class="flex flex-col gap-2">
<div class="flex gap-2">

View File

@@ -31,10 +31,11 @@
@endif
@if ($resource->persistentStorages()->get()->count() > 0)
<h3 class="pt-4">Volumes</h3>
<livewire:project.shared.storages.all :resource="$resource" />
@endif
@if ($fileStorage->count() > 0)
<div class="flex flex-col gap-4 pt-4">
<div class="flex flex-col gap-2">
@foreach ($fileStorage->sort() as $fileStorage)
<livewire:project.service.file-storage :fileStorage="$fileStorage"
wire:key="resource-{{ $fileStorage->uuid }}" />

View File

@@ -2,15 +2,10 @@
<h2>Danger Zone</h2>
<div class="">Woah. I hope you know what are you doing.</div>
<h4 class="pt-4">Delete Resource</h4>
<div class="pb-4">This will stop your containers, delete all related data, etc. Beware! There is no coming
back!
<div class="pb-4">This will stop your containers, delete all related data, etc. Beware! There is no coming back!
</div>
<x-modal-confirmation isErrorButton buttonTitle="Delete" confirm={{ $confirm }}>
<div class="px-2">This resource will be deleted. It is not reversible. <strong class="text-error">Please think
again.</strong><br><br></div>
<h4>Actions</h4>
<x-forms.checkbox id="delete_configurations"
label="Permanently delete configuration files from the server?"></x-forms.checkbox>
<x-forms.checkbox id="delete_volumes" label="Permanently delete associated volumes?"></x-forms.checkbox>
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Resource Deletion?" buttonTitle="Delete" isErrorButton submitAction="delete"
buttonTitle="Delete" :checkboxes="$checkboxes" :actions="['Permanently delete all containers of this resource.']" confirmationText="{{ $resourceName }}"
confirmationLabel="Please confirm the execution of the actions by entering the Resource Name below"
shortConfirmationLabel="Resource Name" step3ButtonText="Permanently Delete" />
</div>

View File

@@ -19,9 +19,6 @@
<div class="box-description">
Network: {{ data_get($resource, 'destination.network') }}
</div>
@if ($resource->server_status == false)
<div class="text-xs font-bold text-error"> This server has connection problems. </div>
@endif
</div>
@if ($resource?->additional_networks?->count() > 0)
<div class="flex gap-2">
@@ -66,11 +63,16 @@
wire:click="stop('{{ data_get($destination, 'server.id') }}')">Stop</x-forms.button>
@endif
<x-modal-confirmation
action="removeServer({{ data_get($destination, 'id') }},{{ data_get($destination, 'server.id') }})"
isErrorButton buttonTitle="Remove Server">
This will stop the running application in this server and remove it as a deployment
destination.<br><br>Please think again.
</x-modal-confirmation>
title="Confirm server removal?"
isErrorButton
buttonTitle="Remove Server"
submitAction="removeServer({{ data_get($destination, 'id') }},{{ data_get($destination, 'server.id') }})"
:actions="['This will stop the all running applications on this server and remove it as a deployment destination.']"
confirmationText="{{ data_get($destination, 'server.name') }}"
confirmationLabel="Please confirm the execution of the actions by entering the Server Name below"
shortConfirmationLabel="Server Name"
step3ButtonText="Permanently Remove Server"
/>
</div>
</div>
@endforeach

View File

@@ -4,7 +4,9 @@
<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?" />
<x-forms.checkbox id="is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" />
@endif
<x-forms.checkbox id="is_multiline" label="Is Multiline?" />
@if (!$shared)

View File

@@ -2,17 +2,19 @@
<div>
<div class="flex items-center gap-2">
<h2>Environment Variables</h2>
<x-modal-input buttonTitle="+ Add" title="New Environment Variable">
<livewire:project.shared.environment-variable.add />
</x-modal-input>
<div class="flex flex-col items-center">
<x-modal-input buttonTitle="+ Add" title="New Environment Variable">
<livewire:project.shared.environment-variable.add />
</x-modal-input>
</div>
<x-forms.button
wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button>
wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view (required to set variables at build time)' }}</x-forms.button>
</div>
<div>Environment variables (secrets) for this resource.</div>
<div>Environment variables (secrets) for this resource. </div>
@if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose')
<div class="w-64 pt-2">
<x-forms.checkbox id="resource.settings.is_env_sorting_enabled" label="Sort alphabetically"
helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order."
helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order (like you pasted them or in the order you created them)."
instantSave></x-forms.checkbox>
</div>
@endif
@@ -31,6 +33,10 @@
@endif
</div>
@if ($view === 'normal')
<div>
<h3>Production Environment Variables</h3>
<div>Environment (secrets) variables for Production.</div>
</div>
@forelse ($resource->environment_variables as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" :type="$resource->type()" />
@@ -39,7 +45,7 @@
@endforelse
@if ($resource->type() === 'application' && $resource->environment_variables_preview->count() > 0 && $showPreview)
<div>
<h3>Preview Deployments</h3>
<h3>Preview Deployments Environment Variables</h3>
<div>Environment (secrets) variables for Preview Deployments.</div>
</div>
@foreach ($resource->environment_variables_preview as $env)
@@ -48,16 +54,15 @@
@endforeach
@endif
@else
<form wire:submit='saveVariables(false)' class="flex flex-col gap-2">
<x-forms.textarea rows="10" class="whitespace-pre-wrap" id="variables"></x-forms.textarea>
<x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button>
<form wire:submit.prevent='submit' class="flex flex-col gap-2">
<x-forms.textarea rows="10" class="whitespace-pre-wrap" id="variables" wire:model="variables" label="Production Environment Variables"></x-forms.textarea>
@if ($showPreview)
<x-forms.textarea rows="10" class="whitespace-pre-wrap" label="Preview Deployments Environment Variables"
id="variablesPreview" wire:model="variablesPreview"></x-forms.textarea>
@endif
<x-forms.button type="submit" class="btn btn-primary">Save All Environment Variables</x-forms.button>
</form>
@if ($showPreview)
<form wire:submit='saveVariables(true)' class="flex flex-col gap-2">
<x-forms.textarea rows="10" class="whitespace-pre-wrap" label="Preview Environment Variables"
id="variablesPreview"></x-forms.textarea>
<x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button>
</form>
@endif
@endif
</div>
</div>

View File

@@ -1,15 +1,6 @@
<div>
<form wire:submit='submit'
class="flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base dark:border-coolgray-300">
{{-- @if (!$env->isFoundInCompose && !$isSharedVariable)
<div class="flex items-center justify-center gap-2 dark:text-warning text-coollabs"> <svg
class="hidden w-4 h-4 dark:text-warning lg:block" 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">
</path>
</svg>This variable is not found in the compose file, so it won't be used.</div>
@endif --}}
@if ($isLocked)
<div class="flex flex-1 w-full gap-2">
<x-forms.input disabled id="env.key" />
@@ -20,10 +11,11 @@
<path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0-2 0m-3-5V7a4 4 0 1 1 8 0v4" />
</g>
</svg>
<x-modal-confirmation isErrorButton buttonTitle="Delete">
You will delete environment variable <span
class="font-bold dark:text-warning text-coollabs">{{ $env->key }}</span>.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton buttonTitle="Delete"
submitAction="delete" :actions="['The selected environment variable will be permanently deleted.']" confirmationText="{{ $env->key }}"
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false"
step2ButtonText="Permanently Delete" />
</div>
@else
@if ($isDisabled)
@@ -50,10 +42,14 @@
@endif
<div class="flex flex-col w-full gap-2 lg:flex-row">
@if ($type === 'service')
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />
<x-forms.checkbox instantSave id="env.is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" />
@else
@if ($env->is_shared)
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />
<x-forms.checkbox instantSave id="env.is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" />
<x-forms.checkbox instantSave id="env.is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@@ -61,7 +57,9 @@
@if ($isSharedVariable)
<x-forms.checkbox instantSave id="env.is_multiline" label="Is Multiline?" />
@else
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />
<x-forms.checkbox instantSave id="env.is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for dockerfile, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" />
<x-forms.checkbox instantSave id="env.is_multiline" label="Is Multiline?" />
@if (!data_get($env, 'is_multiline'))
<x-forms.checkbox instantSave id="env.is_literal"
@@ -79,10 +77,12 @@
<x-forms.button wire:click='lock'>
Lock
</x-forms.button>
<x-modal-confirmation isErrorButton buttonTitle="Delete">
You will delete environment variable <span
class="font-bold dark:text-warning">{{ $env->key }}</span>.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton
buttonTitle="Delete" submitAction="delete" :actions="['The selected environment variable will be permanently deleted.']"
confirmationText="{{ $env->key }}"
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false"
step2ButtonText="Permanently Delete" />
@else
<x-forms.button type="submit">
Update
@@ -90,10 +90,12 @@
<x-forms.button wire:click='lock'>
Lock
</x-forms.button>
<x-modal-confirmation buttonFullWidth isErrorButton buttonTitle="Delete">
You will delete environment variable <span
class="font-bold dark:text-warning">{{ $env->key }}</span>.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton
buttonTitle="Delete" submitAction="delete" :actions="['The selected environment variable will be permanently deleted.']"
confirmationText="{{ $env->key }}"
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false"
step2ButtonText="Permanently Delete" />
@endif
</div>
@endif

View File

@@ -4,57 +4,39 @@
</x-slot>
<livewire:project.shared.configuration-checker :resource="$resource" />
@if ($type === 'application')
<h1>Execute Command</h1>
<h1>Terminal</h1>
<livewire:project.application.heading :application="$resource" />
<h2 class="pt-4">Command</h2>
<div class="pb-2">Run any one-shot command inside a container.</div>
@elseif ($type === 'database')
<h1>Execute Command</h1>
<h1>Terminal</h1>
<livewire:project.database.heading :database="$resource" />
<h2 class="pt-4">Command</h2>
<div class="pb-2">Run any one-shot command inside a container.</div>
@elseif ($type === 'service')
<h2>Execute Command</h2>
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" title="Terminal" />
@endif
<div x-init="$wire.loadContainers">
<div class="pt-4" wire:loading wire:target='loadContainers'>
Loading containers...
Loading resources...
</div>
<div wire:loading.remove wire:target='loadContainers'>
@if (count($containers) > 0)
<form class="flex flex-col gap-2 pt-4" wire:submit='runCommand'>
<div class="flex gap-2">
<x-forms.input placeholder="ls -l" autofocus id="command" label="Command" required />
<x-forms.input id="workDir" label="Working directory" />
</div>
<form class="flex flex-col gap-2 justify-center pt-4 xl:items-end xl:flex-row"
wire:submit="$dispatchSelf('connectToContainer')">
<x-forms.select label="Container" id="container" required>
<option disabled selected>Select container</option>
@if (data_get($this->parameters, 'application_uuid'))
@foreach ($containers as $container)
<option value="{{ data_get($container, 'container.Names') }}">
{{ data_get($container, 'container.Names') }}
</option>
@endforeach
@elseif(data_get($this->parameters, 'service_uuid'))
@foreach ($containers as $container)
<option value="{{ $container }}">
{{ $container }}
</option>
@endforeach
@else
<option value="{{ $container }}">
{{ $container }}
@foreach ($containers as $container)
<option value="{{ data_get($container, 'container.Names') }}">
{{ data_get($container, 'container.Names') }}
({{ data_get($container, 'server.name') }})
</option>
@endif
@endforeach
</x-forms.select>
<x-forms.button type="submit">Run</x-forms.button>
<x-forms.button type="submit">Connect</x-forms.button>
</form>
@else
<div class="pt-4">No containers are not running.</div>
<div class="pt-4">No containers are running.</div>
@endif
</div>
</div>
<div class="w-full pt-10 mx-auto">
<livewire:activity-monitor header="Command output" />
<div class="mx-auto w-full">
<livewire:project.shared.terminal />
</div>
</div>

View File

@@ -33,7 +33,7 @@
screen.scrollTop = 0;
}
}">
<div class="flex items-center gap-2 ">
<div class="flex gap-2 items-center">
@if ($resource?->type() === 'application' || str($resource?->type())->startsWith('standalone'))
<h4>{{ $container }}</h4>
@else
@@ -46,9 +46,9 @@
<x-loading wire:poll.2000ms='getLogs(true)' />
@endif
</div>
<form wire:submit='getLogs(true)' class="flex items-end gap-2 ">
<form wire:submit='getLogs(true)' class="flex gap-2 items-end">
<div class="w-96">
<x-forms.input label="Only Show Number of Lines" placeholder="1000" required
<x-forms.input label="Only Show Number of Lines" placeholder="1000" type="number" required
id="numberOfLines"></x-forms.input>
</div>
<x-forms.button type="submit">Refresh</x-forms.button>
@@ -56,7 +56,7 @@
<x-forms.checkbox instantSave label="Include Timestamps" id="showTimeStamps"></x-forms.checkbox>
</form>
<div :class="fullscreen ? 'fullscreen' : 'relative w-full py-4 mx-auto'">
<div class="flex flex-col-reverse w-full px-4 py-2 overflow-y-auto bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300"
<div class="flex overflow-y-auto flex-col-reverse px-4 py-2 w-full bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300"
:class="fullscreen ? '' : 'max-h-96 border border-solid rounded'">
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4"
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24"
@@ -76,7 +76,7 @@
stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
</svg></button>
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-5 right-1"
<button title="Fullscreen" x-show="!fullscreen" class="absolute right-1 top-5"
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none">

View File

@@ -8,7 +8,20 @@
@foreach ($servers->sortBy('id') as $server)
<h5>Server: <span class="font-bold text-dark dark:text-white">{{ $server->name }}</span></h5>
@foreach ($server->destinations() as $destination)
<x-modal-confirmation action="cloneTo({{ data_get($destination, 'id') }})">
<x-modal-confirmation
title="Clone Resource?"
buttonTitle="Clone Resource"
submitAction="cloneTo({{ data_get($destination, 'id') }})"
:actions="[
'All containers of this resource will be duplicated and cloned to the selected destination.'
]"
:confirmWithText="false"
:confirmWithPassword="false"
step2ButtonText="Clone Resource"
dispatchEvent="true"
dispatchEventType="success"
dispatchEventMessage="Resource cloned to {{ $destination->name }} destination."
>
<x:slot name="content">
<div class="box group">
<div class="flex flex-col">
@@ -17,7 +30,6 @@
</div>
</div>
</x:slot>
<div>You are about to clone this resource.</div>
</x-modal-confirmation>
@endforeach
@endforeach
@@ -36,8 +48,21 @@
<h5>Project: <span class="font-bold text-dark dark:text-white">{{ $project->name }}</span></h5>
@foreach ($project->environments as $environment)
<x-modal-confirmation action="moveTo({{ data_get($environment, 'id') }})">
<x:slot name="content">
<x-modal-confirmation
title="Move Resource?"
buttonTitle="Move Resource"
submitAction="moveTo({{ data_get($environment, 'id') }})"
:actions="[
'All containers of this resource will be moved to the selected environment.'
]"
:confirmWithText="false"
:confirmWithPassword="false"
step2ButtonText="Move Resource"
dispatchEvent="true"
dispatchEventType="success"
dispatchEventMessage="Resource moved to {{ $environment->name }} environment."
>
<x:slot:content>
<div class="box group">
<div class="flex flex-col">
<div class="box-title">Environment</div>
@@ -45,7 +70,6 @@
</div>
</div>
</x:slot>
<div>You are about to move this resource.</div>
</x-modal-confirmation>
@endforeach
@empty

View File

@@ -13,7 +13,7 @@
</x-forms.select>
@else
<x-forms.input placeholder="php" id="container"
helper="You can leave it empty if your resource only have one container." label="Container name" />
helper="You can leave this empty if your resource only has one container." label="Container name" />
@endif
@elseif ($type === 'service')
<x-forms.select id="container" label="Container name">

View File

@@ -1,32 +1,38 @@
<div class="flex flex-col-reverse gap-2">
<div class="flex flex-col gap-4">
@forelse($executions as $execution)
@if (data_get($execution, 'id') == $selectedKey)
<div class="p-2">
@if (data_get($execution, 'message'))
<div>
<pre>{{ data_get($execution, 'message') }}</pre>
</div>
@else
<div>No output was recorded for this execution.</div>
@endif
</div>
@endif
<a wire:click="selectTask({{ data_get($execution, 'id') }})" @class([
'flex flex-col border-l transition-colors box-without-bg bg-coolgray-100 hover:bg-coolgray-200 cursor-pointer',
'bg-coolgray-200 dark:text-white hover:bg-coolgray-200' =>
'flex flex-col border-l-2 transition-colors p-4 cursor-pointer',
'bg-white hover:bg-gray-100 dark:bg-coolgray-100 dark:hover:bg-coolgray-200',
'text-black dark:text-white',
'bg-gray-200 dark:bg-coolgray-200' =>
data_get($execution, 'id') == $selectedKey,
'border-green-500' => data_get($execution, 'status') === 'success',
'border-red-500' => data_get($execution, 'status') === 'failed',
'border-yellow-500' => data_get($execution, 'status') === 'running',
])>
@if (data_get($execution, 'status') === 'running')
<div class="absolute top-2 right-2">
<x-loading />
</div>
@endif
<div>Status: {{ data_get($execution, 'status') }}</div>
<div>Started At: {{ data_get($execution, 'created_at') }}</div>
<div class="text-gray-700 dark:text-gray-300 font-semibold mb-1">Status: {{ data_get($execution, 'status') }}
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Started At: {{ $this->formatDateInServerTimezone(data_get($execution, 'created_at', now())) }}
</div>
</a>
@if (data_get($execution, 'id') == $selectedKey)
<div class="p-4 mb-2 bg-gray-100 dark:bg-coolgray-200 rounded">
@if (data_get($execution, 'message'))
<div>
<pre class="whitespace-pre-wrap">{{ data_get($execution, 'message') }}</pre>
</div>
@else
<div>No output was recorded for this execution.</div>
@endif
</div>
@endif
@empty
<div>No executions found.</div>
<div class="p-4 bg-gray-100 dark:bg-coolgray-100 rounded">No executions found.</div>
@endforelse
</div>

View File

@@ -11,37 +11,40 @@
<form wire:submit="submit" class="w-full">
<div class="flex flex-col gap-2 pb-2">
<div class="flex items-end gap-2 pt-4">
<div class="flex gap-2 items-end pt-4">
<h2>Scheduled Task</h2>
<x-forms.button type="submit">
Save
</x-forms.button>
<x-modal-confirmation isErrorButton buttonTitle="Delete Scheduled Task">
You will delete scheduled task <span class="font-bold dark:text-warning">{{ $task->name }}</span>.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Scheduled Task Deletion?" isErrorButton buttonTitle="Delete"
submitAction="delete({{ $task->id }})" :actions="['The selected scheduled task will be permanently deleted.']" confirmationText="{{ $task->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Scheduled Task Name below"
shortConfirmationLabel="Scheduled Task Name" :confirmWithPassword="false"
step2ButtonText="Permanently Delete" />
</div>
<div class="w-48">
<x-forms.checkbox instantSave id="task.enabled" label="Enabled" />
</div>
</div>
<div class="flex w-full gap-2">
<x-forms.input placeholder="Name" id="task.name" label="Name" required />
<x-forms.input placeholder="php artisan schedule:run" id="task.command" label="Command" required />
<x-forms.input placeholder="0 0 * * * or daily" id="task.frequency" label="Frequency" required />
@if ($type === 'application')
<x-forms.input placeholder="php"
helper="You can leave it empty if your resource only have one container." id="task.container"
label="Container name" />
@elseif ($type === 'service')
<x-forms.input placeholder="php"
helper="You can leave it empty if your resource only have one service in your stack. Otherwise use the stack name, without the random generated id. So if you have a mysql service in your stack, use mysql."
id="task.container" label="Service name" />
@endif
</div>
<div class="flex gap-2 w-full">
<x-forms.input placeholder="Name" id="task.name" label="Name" required />
<x-forms.input placeholder="php artisan schedule:run" id="task.command" label="Command" required />
<x-forms.input placeholder="0 0 * * * or daily" id="task.frequency" label="Frequency" required />
@if ($type === 'application')
<x-forms.input placeholder="php"
helper="You can leave this empty if your resource only has one container." id="task.container"
label="Container name" />
@elseif ($type === 'service')
<x-forms.input placeholder="php"
helper="You can leave this empty if your resource only has one service in your stack. Otherwise use the stack name, without the random generated ID. So if you have a mysql service in your stack, use mysql."
id="task.container" label="Service name" />
@endif
</div>
</form>
<div class="pt-4">
<h3 class="py-4">Recent executions <span class="text-xs text-neutral-500">(click to check output)</span></h3>
<livewire:project.shared.scheduled-task.executions key="{{ $task->id }}" selectedKey="" :executions="$task->executions->take(-20)" />
<livewire:project.shared.scheduled-task.executions :task="$task" key="{{ $task->id }}" selectedKey=""
:executions="$task->executions->take(20)" />
</div>
</div>

View File

@@ -1,43 +1,47 @@
<div class="flex flex-col w-full gap-2 rounded">
You can add Volumes, Files and Directories to your resources here.
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submitPersistentVolume'>
<h3>Volume Mount</h3>
@if ($isSwarm)
<h5>Swarm Mode detected: You need to set a shared volume (EFS/NFS/etc) on all the worker nodes if you would
like to use a persistent volumes.</h5>
@endif
<x-forms.input placeholder="pv-name" id="name" label="Name" required helper="Volume name." />
@if ($isSwarm)
<x-forms.input placeholder="/root" id="host_path" label="Source Path" required
<div class="flex flex-col w-full gap-2 rounded max-h-[80vh] overflow-y-auto scrollbar">
<div class="p-4">
You can add Volumes, Files and Directories to your resources here.
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submitPersistentVolume'>
<h3>Volume Mount</h3>
@if ($isSwarm)
<h5>Swarm Mode detected: You need to set a shared volume (EFS/NFS/etc) on all the worker nodes if you
would
like to use a persistent volumes.</h5>
@endif
<x-forms.input placeholder="pv-name" id="name" label="Name" required helper="Volume name." />
@if ($isSwarm)
<x-forms.input placeholder="/root" id="host_path" label="Source Path" required
helper="Directory on the host system." />
@else
<x-forms.input placeholder="/root" id="host_path" label="Source Path"
helper="Directory on the host system." />
@endif
<x-forms.input placeholder="/tmp/root" id="mount_path" label="Destination Path" required
helper="Directory inside the container." />
<x-forms.button type="submit" @click="modalOpen=false">
Save
</x-forms.button>
</form>
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submitFileStorage'>
<h3>File Mount</h3>
<x-forms.input placeholder="/etc/nginx/nginx.conf" id="file_storage_path" label="Destination Path" required
helper="File inside the container" />
<x-forms.textarea label="Content" id="file_storage_content"></x-forms.textarea>
<x-forms.button type="submit" @click="modalOpen=false">
Save
</x-forms.button>
</form>
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submitFileStorageDirectory'>
<h3>Directory Mount</h3>
<x-forms.input placeholder="{{ application_configuration_dir() }}/{{ $resource->uuid }}/etc/nginx"
id="file_storage_directory_source" label="Source Directory" required
helper="Directory on the host system." />
@else
<x-forms.input placeholder="/root" id="host_path" label="Source Path"
helper="Directory on the host system." />
@endif
<x-forms.input placeholder="/tmp/root" id="mount_path" label="Destination Path" required
helper="Directory inside the container." />
<x-forms.button type="submit" @click="modalOpen=false">
Save
</x-forms.button>
</form>
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submitFileStorage'>
<h3>File Mount</h3>
<x-forms.input placeholder="/etc/nginx/nginx.conf" id="file_storage_path" label="Destination Path" required
helper="File inside the container" />
<x-forms.textarea label="Content" id="file_storage_content"></x-forms.textarea>
<x-forms.button type="submit" @click="modalOpen=false">
Save
</x-forms.button>
</form>
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submitFileStorageDirectory'>
<h3>Directory Mount</h3>
<x-forms.input placeholder="{{ application_configuration_dir() }}/{{ $resource->uuid }}/etc/nginx"
id="file_storage_directory_source" label="Source Directory" required
helper="Directory on the host system." />
<x-forms.input placeholder="/etc/nginx" id="file_storage_directory_destination" label="Destination Directory"
required helper="Directory inside the container." />
<x-forms.button type="submit" @click="modalOpen=false">
Save
</x-forms.button>
</form>
<x-forms.input placeholder="/etc/nginx" id="file_storage_directory_destination"
label="Destination Directory" required helper="Directory inside the container." />
<x-forms.button type="submit" @click="modalOpen=false">
Save
</x-forms.button>
</form>
</div>
</div>

View File

@@ -46,13 +46,13 @@
<x-forms.button type="submit">
Update
</x-forms.button>
<x-modal-confirmation isErrorButton buttonTitle="Delete">
This storage will be deleted <span class="font-bold dark:text-warning">{{ $storage->name }}</span>.
It
is
not
reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm persistent storage deletion?" isErrorButton buttonTitle="Delete"
submitAction="delete" :actions="[
'The selected persistent storage/volume will be permanently deleted.',
'If the persistent storage/volume is actvily used by a resource data will be lost.',
]" confirmationText="{{ $storage->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Storage Name below"
shortConfirmationLabel="Storage Name" step3ButtonText="Permanently Delete" />
</div>
@endif
</form>

View File

@@ -0,0 +1,240 @@
<div x-data="data()">
{{-- <div x-show="!terminalActive" class="flex items-center justify-center w-full py-4 mx-auto h-[510px]">
<div class="p-1 w-full h-full rounded border dark:bg-coolgray-100 dark:border-coolgray-300">
<span class="font-mono text-sm text-gray-500" x-text="message"></span>
</div>
</div> --}}
<div x-ref="terminalWrapper"
:class="fullscreen ? 'fullscreen' : 'relative w-full h-full py-4 mx-auto max-h-[510px]'">
<div id="terminal" wire:ignore></div>
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4 text-white" x-on:click="makeFullscreen"><svg
class="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="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg></button>
<button title="Fullscreen" x-show="!fullscreen && terminalActive" class="absolute right-4 top-6 text-white"
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none">
<path
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
<path fill="currentColor"
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
</g>
</svg></button>
</div>
@script
<script>
const MAX_PENDING_WRITES = 5;
let pendingWrites = 0;
let paused = false;
let socket;
let commandBuffer = '';
function keepAlive() {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({
ping: true
}));
}
}
const keepAliveInterval = setInterval(keepAlive, 30000);
// Clear the interval when the component is destroyed
document.addEventListener('livewire:navigating', () => {
clearInterval(keepAliveInterval);
});
function initializeWebSocket() {
if (!socket || socket.readyState === WebSocket.CLOSED) {
const predefined = {
protocol: "{{ env('TERMINAL_PROTOCOL') }}",
host: "{{ env('TERMINAL_HOST') }}",
port: "{{ env('TERMINAL_PORT') }}"
}
const connectionString = {
protocol: window.location.protocol === 'https:' ? 'wss' : 'ws',
host: window.location.hostname,
port: ":6002",
path: '/terminal/ws'
}
if (!window.location.port) {
connectionString.port = ''
}
if (predefined.host) {
connectionString.host = predefined.host
}
if (predefined.port) {
connectionString.port = `:${predefined.port}`
}
if (predefined.protocol) {
connectionString.protocol = predefined.protocol
}
const url =
`${connectionString.protocol}://${connectionString.host}${connectionString.port}${connectionString.path}`
socket = new WebSocket(url);
socket.onmessage = handleSocketMessage;
socket.onerror = (e) => {
console.error('WebSocket error:', e);
};
socket.onclose = () => {
console.log('WebSocket connection closed');
};
}
}
function handleSocketMessage(event) {
$data.message = '(connection closed)';
// Initialize Terminal
if (event.data === 'pty-ready') {
term.open(document.getElementById('terminal'));
$data.terminalActive = true;
term.reset();
term.focus();
document.querySelector('.xterm-viewport').classList.add('scrollbar', 'rounded')
$data.resizeTerminal()
} else if (event.data === 'unprocessable') {
term.reset();
$data.terminalActive = false;
$data.message = '(sorry, something went wrong, please try again)';
} else {
pendingWrites++;
term.write(event.data, flowControlCallback);
}
}
function flowControlCallback() {
pendingWrites--;
if (pendingWrites > MAX_PENDING_WRITES && !paused) {
paused = true;
socket.send(JSON.stringify({
pause: true
}));
return;
}
if (pendingWrites <= MAX_PENDING_WRITES && paused) {
paused = false;
socket.send(JSON.stringify({
resume: true
}));
return;
}
}
term.onData((data) => {
socket.send(JSON.stringify({
message: data
}));
// Type CTRL + D or exit in the terminal
if (data === '\x04' || (data === '\r' && stripAnsiCommands(commandBuffer).trim().includes('exit'))) {
checkIfProcessIsRunningAndKillIt();
setTimeout(() => {
$data.terminalActive = false;
term.reset();
}, 500);
commandBuffer = '';
} else if (data === '\r') {
commandBuffer = '';
} else {
commandBuffer += data;
}
});
function stripAnsiCommands(input) {
return input.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
}
// Copy and paste
// Enables ctrl + c and ctrl + v
// defaults otherwise to ctrl + insert, shift + insert
term.attachCustomKeyEventHandler((arg) => {
if (arg.ctrlKey && arg.code === "KeyV" && arg.type === "keydown") {
navigator.clipboard.readText()
.then(text => {
socket.send(JSON.stringify({
message: text
}));
})
};
if (arg.ctrlKey && arg.code === "KeyC" && arg.type === "keydown") {
const selection = term.getSelection();
if (selection) {
navigator.clipboard.writeText(selection);
return false;
}
}
return true;
});
$wire.on('send-back-command', function(command) {
socket.send(JSON.stringify({
command: command
}));
});
window.addEventListener('beforeunload', function(e) {
checkIfProcessIsRunningAndKillIt();
});
function checkIfProcessIsRunningAndKillIt() {
socket.send(JSON.stringify({
checkActive: 'force'
}));
}
window.onresize = function() {
$data.resizeTerminal()
};
Alpine.data('data', () => ({
fullscreen: false,
terminalActive: false,
message: '(connection closed)',
init() {
this.$watch('terminalActive', (value) => {
this.$nextTick(() => {
if (value) {
$refs.terminalWrapper.style.display = 'block';
this.resizeTerminal();
} else {
$refs.terminalWrapper.style.display = 'none';
}
});
});
},
makeFullscreen() {
this.fullscreen = !this.fullscreen;
$nextTick(() => {
this.resizeTerminal()
})
},
resizeTerminal() {
if (!this.terminalActive) return;
fitAddon.fit();
const height = $refs.terminalWrapper.clientHeight;
const rows = height / term._core._renderService._charSizeService.height - 1;
var termWidth = term.cols;
var termHeight = parseInt(rows.toString(), 10);
term.resize(termWidth, termHeight);
socket.send(JSON.stringify({
resize: {
cols: termWidth,
rows: termHeight
}
}));
}
}));
initializeWebSocket();
</script>
@endscript
</div>

View File

@@ -1,19 +0,0 @@
<div>
<form class="flex flex-col justify-center gap-2 xl:items-end xl:flex-row" wire:submit='runCommand'>
<x-forms.input placeholder="ls -l" autofocus id="command" label="Command" required />
<x-forms.select label="Server" id="server" required>
@foreach ($servers as $server)
@if ($loop->first)
<option selected value="{{ $server->uuid }}">{{ $server->name }}</option>
@else
<option value="{{ $server->uuid }}">{{ $server->name }}</option>
@endif
@endforeach
</x-forms.select>
<x-forms.button type="submit">Execute Command
</x-forms.button>
</form>
<div class="w-full pt-10 mx-auto">
<livewire:activity-monitor header="Command output" />
</div>
</div>

View File

@@ -56,12 +56,18 @@
@endif
</div>
<x-modal-confirmation isErrorButton action="revoke({{ data_get($token, 'id') }})">
<x-slot:button-title>
Revoke token
</x-slot:button-title>
This API Token will be deleted and anything using it will fail. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation
title="Confirm API Token Revocation?"
isErrorButton
buttonTitle="Revoke token"
submitAction="revoke({{ data_get($token, 'id') }})"
:actions="['This API Token will be revoked and permanently deleted.', 'Any API call made with this token will fail.']"
confirmationText="{{ $token->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the API Token Description below"
shortConfirmationLabel="API Token Description"
:confirmWithPassword="false"
step2ButtonText="Revoke API Token"
/>
</div>
@empty
<div>

View File

@@ -4,8 +4,8 @@
<div class="font-bold">You should not use passphrase protected keys.</div>
</div>
<div class="flex gap-2 mb-4">
<x-forms.button wire:click="generateNewRSAKey">Generate new RSA SSH Key</x-forms.button>
<x-forms.button wire:click="generateNewEDKey">Generate new ED25519 SSH Key</x-forms.button>
<x-forms.button wire:click="generateNewEDKey">Generate new ED25519 SSH Key (Recommended, fastest and most secure)</x-forms.button>
<x-forms.button wire:click="generateNewRSAKey">Generate new RSA SSH Key</x-forms.button>
</div>
<form class="flex flex-col gap-2" wire:submit='createPrivateKey'>
<div class="flex gap-2">

View File

@@ -1,13 +1,20 @@
<x-layout>
<x-slot:title>
Private Keys | Coolify
</x-slot>
<div>
<x-security.navbar />
<div class="flex gap-2">
<h2 class="pb-4">Private Keys</h2>
<x-modal-input buttonTitle="+ Add" title="New Private Key">
<livewire:security.private-key.create />
</x-modal-input>
<x-modal-confirmation
title="Confirm unused SSH Key Deletion?"
buttonTitle="Delete unused SSH Keys"
isErrorButton
submitAction="cleanupUnusedKeys"
:actions="['All unused SSH keys (marked with unused) are permanently deleted.']"
:confirmWithText="false"
:confirmWithPassword="false"
/>
</div>
<div class="grid gap-2 lg:grid-cols-2">
@forelse ($privateKeys as $key)
@@ -19,11 +26,15 @@
</div>
<div class="box-description">
{{ $key->description }}
@if (!$key->isInUse())
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-400 text-black">Unused</span>
@endif
</div>
</div>
</a>
@empty
<div>No private keys found.</div>
@endforelse
</div>
</x-layout>
</div>

View File

@@ -11,9 +11,18 @@
Save
</x-forms.button>
@if (data_get($private_key, 'id') > 0)
<x-modal-confirmation isErrorButton buttonTitle="Delete">
This private key will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation
title="Confirm Private Key Deletion?"
isErrorButton
buttonTitle="Delete"
submitAction="delete({{ $private_key->id }})"
:actions="['This private key will be permanently deleted.', 'All servers connected to this private key will stop working.', 'Any git app using this private key will stop working.']"
confirmationText="{{ $private_key->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Private Key Name below"
shortConfirmationLabel="Private Key Name"
:confirmWithPassword="false"
step2ButtonText="Delete Private Key"
/>
@endif
</div>
<x-forms.input id="private_key.name" label="Name" required />

View File

@@ -1,8 +1,6 @@
<form wire:submit.prevent='submit' class="flex flex-col w-full gap-2">
<x-forms.input id="cloudflare_token" required label="Cloudflare Token" />
<form wire:submit.prevent='submit' class="flex flex-col gap-2 w-full">
<x-forms.input id="cloudflare_token" required label="Cloudflare Token" type="password" />
<x-forms.input id="ssh_domain" label="Configured SSH Domain" required
helper="The SSH Domain you configured in Cloudflare" />
<x-forms.button type="submit" isHighlighted @click="modalOpen=false">Automated Configuration (experimental)</x-forms.button>
<h3 class="text-center">Or</h3>
<x-forms.button wire:click.prevent='alreadyConfigured' @click="modalOpen=false">I have already set up the tunnel manually on the server.</x-forms.button>
helper="The SSH domain you configured in Cloudflare. Make sure there is no protocol like http(s):// so you provide a FQDN not a URL. <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels/#automated' target='_blank'>Documentation</a>" />
<x-forms.button type="submit" isHighlighted @click="modalOpen=false">Continue</x-forms.button>
</form>

View File

@@ -8,13 +8,15 @@
</div>
@if ($server->definedResources()->count() > 0)
<div class="pb-2 text-red-500">You need to delete all resources before deleting this server.</div>
<x-modal-confirmation disabled isErrorButton buttonTitle="Delete">
This server will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Server Deletion?" isErrorButton buttonTitle="Delete" submitAction="delete"
:actions="['This server will be permanently deleted.']" confirmationText="{{ $server->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Server Name below"
shortConfirmationLabel="Server Name" step3ButtonText="Permanently Delete" />
@else
<x-modal-confirmation isErrorButton buttonTitle="Delete">
This server will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Server Deletion?" isErrorButton buttonTitle="Delete"
submitAction="delete" :actions="['This server will be permanently deleted.']" confirmationText="{{ $server->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Server Name below"
shortConfirmationLabel="Server Name" step2ButtonText="Permanently Delete" />
@endif
@endif
</div>

View File

@@ -3,11 +3,10 @@
<div class="flex gap-2">
<h2>General</h2>
@if ($server->id === 0)
<x-modal-confirmation buttonTitle="Save" title="Change Localhost" action="submit">
You could lose a lot of functionalities if you change the server details of the server where Coolify
is
running on.<br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Server Settings Change?" buttonTitle="Save" submitAction="submit"
:actions="[
'You could lose a lot of functionalities if you change the server details of the server where Coolify is running on.',
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Save" />
@else
<x-forms.button type="submit">Save</x-forms.button>
@if ($server->isFunctional())
@@ -35,7 +34,7 @@
<livewire:server.validate-and-install :server="$server" />
</x-slot:content>
<x-forms.button @click="slideOverOpen=true"
class="w-full mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100"
class="mt-8 mb-4 w-full font-bold box-without-bg bg-coollabs hover:bg-coollabs-100"
wire:click.prevent='validateServer' isHighlighted>
Validate Server & Install Docker Engine
</x-forms.button>
@@ -58,7 +57,7 @@
number of servers for which you have paid.</div>
@endif
<div class="flex flex-col gap-2 pt-4">
<div class="flex flex-col w-full gap-2 lg:flex-row">
<div class="flex flex-col gap-2 w-full lg:flex-row">
<x-forms.input id="server.name" label="Name" required />
<x-forms.input id="server.description" label="Description" />
@if (!$server->settings->is_swarm_worker && !$server->settings->is_build_server)
@@ -67,100 +66,152 @@
@endif
</div>
<div class="flex flex-col w-full gap-2 lg:flex-row">
<div class="flex flex-col gap-2 w-full lg:flex-row">
<x-forms.input type="password" id="server.ip" label="IP Address/Domain"
helper="An IP Address (127.0.0.1) or domain (example.com)." required />
helper="An IP Address (127.0.0.1) or domain (example.com). Make sure there is no protocol like http(s):// so you provide a FQDN not a URL." required />
<div class="flex gap-2">
<x-forms.input id="server.user" label="User" required />
<x-forms.input type="number" id="server.port" label="Port" required />
</div>
</div>
<div class="w-64">
@if ($server->isFunctional())
@if (!$server->isLocalhost())
<x-forms.checkbox instantSave id="server.settings.is_build_server"
label="Use it as a build server?" />
<div class="flex items-center gap-1 pt-6">
<h3 class="">Cloudflare Tunnels
</h3>
<div class="w-full" x-data="{
open: false,
search: '{{ $server->settings->server_timezone ?: '' }}',
timezones: @js($timezones),
placeholder: '{{ $server->settings->server_timezone ? 'Search timezone...' : 'Select Server Timezone' }}',
init() {
this.$watch('search', value => {
if (value === '') {
this.open = true;
}
})
}
}">
<div class="flex items-center mb-1">
<label for="server.settings.server_timezone">Server
Timezone</label>
<x-helper class="ml-2" helper="Server's timezone. This is used for backups, cron jobs, etc." />
</div>
<div class="relative">
<div class="inline-flex relative items-center w-64">
<input wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" x-model="search"
@focus="open = true" @click.away="open = false" @input="open = true" class="w-full input"
:placeholder="placeholder" wire:model.debounce.300ms="server.settings.server_timezone">
<svg class="absolute right-0 mr-2 w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" @click="open = true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9" />
</svg>
</div>
<div x-show="open"
class="overflow-auto overflow-x-hidden absolute z-50 mt-1 w-64 max-h-60 bg-white rounded-md border shadow-lg dark:bg-coolgray-100 dark:border-coolgray-200 scrollbar">
<template
x-for="timezone in timezones.filter(tz => tz.toLowerCase().includes(search.toLowerCase()))"
:key="timezone">
<div @click="search = timezone; open = false; $wire.set('server.settings.server_timezone', timezone)"
class="px-4 py-2 text-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-coolgray-300 dark:text-gray-200"
x-text="timezone"></div>
</template>
</div>
</div>
</div>
<div class="{{ $server->isFunctional() ? 'w-96' : 'w-full' }}">
@if (!$server->isLocalhost())
<x-forms.checkbox instantSave id="server.settings.is_build_server"
label="Use it as a build server?" />
<div class="flex flex-col gap-2 pt-6">
<div class="flex gap-1 items-center">
<h3 class="text-lg font-semibold">Cloudflare Tunnels</h3>
<x-helper class="inline-flex"
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br><span class='dark:text-warning'>Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br> You then can close your server's SSH port in the firewall of your hosting provider.<br><span class='dark:text-warning'>If you choose manual configuration, Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
</div>
@if ($server->settings->is_cloudflare_tunnel)
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" />
@else
<x-modal-input buttonTitle="Configure" title="Cloudflare Tunnels">
<div class="w-64">
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" />
</div>
@elseif (!$server->isFunctional())
<div class="p-4 mb-4 w-full text-sm text-yellow-800 bg-yellow-100 rounded dark:bg-yellow-900 dark:text-yellow-300">
<x-slide-over closeWithX fullScreen>
<x-slot:title>Validate & configure</x-slot:title>
<x-slot:content>
<livewire:server.validate-and-install :server="$server" />
</x-slot:content>
To <span class="font-semibold">automatically</span> configure Cloudflare Tunnels, please click
<span @click="slideOverOpen=true"
wire:click.prevent='validateServer' class="underline cursor-pointer">
here.</span> You will need a Cloudflare token and domain.
</x-slide-over>
<br/>
To <span class="font-semibold">manually</span> configure Cloudflare Tunnels, please click <span wire:click="manualCloudflareConfig" class="underline cursor-pointer">here</span>, then you should validate the server.
<br/><br/>
For more information, please read our <a href="https://coolify.io/docs/knowledge-base/cloudflare/tunnels/" target="_blank" class="font-medium underline hover:text-yellow-600 dark:hover:text-yellow-200">documentation</a>.
</div>
@endif
@if (!$server->settings->is_cloudflare_tunnel && $server->isFunctional())
<x-modal-input buttonTitle="Automated Configuration" title="Cloudflare Tunnels" class="w-full">
<livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
</x-modal-input>
@endif
@if (!$server->isBuildServer())
<h3 class="pt-6">Swarm <span class="text-xs text-neutral-500">(experimental)</span></h3>
<div class="pb-4">Read the docs <a class='underline dark:text-white'
href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.
@if ($server->isFunctional() &&!$server->settings->is_cloudflare_tunnel)
<div wire:click="manualCloudflareConfig" class="w-full underline cursor-pointer">
I have configured Cloudflare Tunnels manually
</div>
@if ($server->settings->is_swarm_worker)
<x-forms.checkbox disabled instantSave type="checkbox"
id="server.settings.is_swarm_manager"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@else
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_manager"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@endif
@endif
@if ($server->settings->is_swarm_manager)
<x-forms.checkbox disabled instantSave type="checkbox"
id="server.settings.is_swarm_worker"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@else
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_worker"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@endif
</div>
@if (!$server->isBuildServer() && !$server->settings->is_cloudflare_tunnel)
<h3 class="pt-6">Swarm <span class="text-xs text-neutral-500">(experimental)</span></h3>
<div class="pb-4">Read the docs <a class='underline dark:text-white'
href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.
</div>
@if ($server->settings->is_swarm_worker)
<x-forms.checkbox disabled instantSave type="checkbox"
id="server.settings.is_swarm_manager"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@else
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_manager"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@endif
@if ($server->settings->is_swarm_manager)
<x-forms.checkbox disabled instantSave type="checkbox"
id="server.settings.is_swarm_worker"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@else
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_worker"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@endif
@endif
@else
<div class="flex items-center gap-1 pt-6">
<h3 class="">Cloudflare Tunnels
</h3>
<x-helper class="inline-flex"
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br><span class='dark:text-warning'>Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
</div>
@if ($server->settings->is_cloudflare_tunnel)
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" />
@else
<x-modal-input buttonTitle="Configure" title="Cloudflare Tunnels">
<livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
</x-modal-input>
@endif
@endif
</div>
</div>
@if ($server->isFunctional())
<h3 class="pt-4">Settings</h3>
<div class="flex flex-col gap-2">
<div class="flex flex-col flex-wrap gap-2 sm:flex-nowrap">
@if ($server->settings->is_force_cleanup_enabled)
<div class="flex flex-col gap-1">
<div class="flex flex-col gap-2">
<div class="flex flex-col flex-wrap gap-2 sm:flex-nowrap">
<div class="w-64">
<x-forms.checkbox
helper="This will cleanup build caches / unused images / etc every 10 minutes."
instantSave id="server.settings.is_force_cleanup_enabled"
label="Force Cleanup Docker Engine" />
helper="Enable force Docker Cleanup. This will cleanup build caches / unused images / etc."
instantSave id="server.settings.force_docker_cleanup" label="Force Docker Cleanup" />
</div>
@else
<x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required
helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
<div class="w-64">
<x-forms.checkbox
helper="This will cleanup build caches / unused images / etc every 10 minutes."
instantSave id="server.settings.is_force_cleanup_enabled"
label="Force Cleanup Docker Engine" />
</div>
@endif
@if ($server->settings->force_docker_cleanup)
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
label="Docker cleanup frequency" required
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every night at midnight." />
@else
<x-forms.input id="server.settings.docker_cleanup_threshold"
label="Docker cleanup threshold (%)" required
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
@endif
</div>
</div>
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
@@ -169,7 +220,7 @@
helper="You can define the maximum duration for a deployment to run before timing it out." />
</div>
</div>
<div class="flex items-center gap-2 pt-4 pb-2">
<div class="flex gap-2 items-center pt-4 pb-2">
<h3>Sentinel</h3>
{{-- @if ($server->isSentinelEnabled()) --}}
{{-- <x-forms.button wire:click='restartSentinel'>Restart</x-forms.button> --}}
@@ -182,12 +233,12 @@
<div class="pt-4">
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
<x-forms.input type="password" id="server.settings.metrics_token" label="Metrics token" required
helper="Token for collector (Sentinel)." />
helper="Token for collector (Sentinel)." />
<x-forms.input id="server.settings.metrics_refresh_rate_seconds" label="Metrics rate (seconds)"
required
helper="The interval for gathering metrics. Lower means more disk space will be used." />
required
helper="The interval for gathering metrics. Lower means more disk space will be used." />
<x-forms.input id="server.settings.metrics_history_days" label="Metrics history (days)" required
helper="How many days should the metrics data should be reserved." />
helper="How many days should the metrics data should be reserved." />
</div>
</div> --}}
@endif

View File

@@ -83,9 +83,9 @@
<x-forms.button class="box" wire:click="selectProxy('CADDY')">
Caddy
</x-forms.button>
<x-forms.button disabled class="box">
{{-- <x-forms.button disabled class="box">
Nginx
</x-forms.button>
</x-forms.button> --}}
</div>
</div>
@endif

View File

@@ -1,10 +1,6 @@
@php use App\Enums\ProxyTypes; @endphp
<div>
@if (
$server->proxyType() !== 'NONE' &&
$server->isFunctional() &&
!$server->isSwarmWorker() &&
!$server->settings->is_build_server)
@if ($server->proxySet())
<x-slide-over closeWithX fullScreen @startproxy.window="slideOverOpen = true">
<x-slot:title>Proxy Status</x-slot:title>
<x-slot:content>
@@ -24,7 +20,17 @@
</a>
</button>
@endif
<x-modal-confirmation @click="$wire.dispatch('restartEvent')">
<x-modal-confirmation
title="Confirm Proxy Restart?"
buttonTitle="Restart Proxy"
submitAction="restart"
:actions="['This proxy will be stopped and started again.', 'All resources hosted on coolify will be unavailable during the restart.']"
:confirmWithText="false"
:confirmWithPassword="false"
step2ButtonText="Restart Proxy"
:dispatchEvent="true"
dispatchEventType="restartEvent"
>
<x-slot:button-title>
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
@@ -35,10 +41,18 @@
</svg>
Restart Proxy
</x-slot:button-title>
This proxy will be stopped and started. It is not reversible. <br>All resources will be unavailable
during the restart. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
<x-modal-confirmation
title="Confirm Proxy Stopping?"
buttonTitle="Stop Proxy"
submitAction="stop(true)"
:actions="['The coolify proxy will be stopped.', 'All resources hosted on coolify will be unavailable.']"
:confirmWithText="false"
:confirmWithPassword="false"
step2ButtonText="Stop Proxy"
:dispatchEvent="true"
dispatchEventType="stopEvent"
>
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -51,8 +65,6 @@
</svg>
Stop Proxy
</x-slot:button-title>
This proxy will be stopped. It is not reversible. <br>All resources will be unavailable.
<br>Please think again.
</x-modal-confirmation>
</div>
@else

View File

@@ -1,20 +1,12 @@
<div x-init="$wire.checkProxy()">
@if (
$server->proxyType() !== 'NONE' &&
$server->isFunctional() &&
!$server->isSwarmWorker() &&
!$server->settings->is_build_server)
<div class="flex gap-2">
@if (data_get($server, 'proxy.status') === 'running')
<x-status.running status="Proxy Running" />
@elseif (data_get($server, 'proxy.status') === 'restarting')
<x-status.restarting status="Proxy Restarting" />
@else
<x-status.stopped status="Proxy Stopped" />
@endif
@if (data_get($server, 'proxy.status') === 'running')
<x-forms.button wire:click='checkProxy(true)'>Refresh</x-forms.button>
@endif
</div>
<div x-init="$wire.checkProxy()" class="flex gap-2">
@if (data_get($server, 'proxy.status') === 'running')
<x-status.running status="Proxy Running" />
@elseif (data_get($server, 'proxy.status') === 'restarting')
<x-status.restarting status="Proxy Restarting" />
@else
<x-status.stopped status="Proxy Stopped" />
@endif
@if (data_get($server, 'proxy.status') === 'running')
<x-forms.button wire:click='checkProxy(true)'>Refresh</x-forms.button>
@endif
</div>

View File

@@ -26,7 +26,8 @@
<h3 class="pb-4">Choose another Key</h3>
<div class="grid grid-cols-3 gap-2">
@forelse ($privateKeys as $private_key)
<div class="box group" wire:click='setPrivateKey({{ $private_key->id }})'>
<div class="box group cursor-pointer"
wire:click='setPrivateKey({{ $private_key->id }})'>
<div class="flex flex-col ">
<div class="box-title">{{ $private_key->name }}</div>
<div class="box-description">{{ $private_key->description }}</div>

View File

@@ -13,16 +13,66 @@
<div>General configuration for your Coolify instance.</div>
<div class="flex flex-col gap-2">
<h4 class="pt-6">Instance Settings</h4>
<div class="flex flex-wrap items-end gap-2">
<h4 class="pt-6">Instance Settings</h4>
<x-forms.input id="settings.fqdn" label="Instance's Domain" placeholder="https://coolify.io" />
<x-forms.input id="settings.instance_name" label="Instance's Name" placeholder="Coolify" />
<div class="flex gap-2 md:flex-row flex-col w-full">
<x-forms.input id="settings.fqdn" label="Instance's Domain"
helper="Enter the full domain name (FQDN) of the instance, including 'https://' if you want to secure the dashboard with HTTPS. Setting this will make the dashboard accessible via this domain, secured by HTTPS, instead of just the IP address."
placeholder="https://coolify.yourdomain.com" />
<x-forms.input id="settings.instance_name" label="Instance's Name" placeholder="Coolify" />
<div class="w-full" x-data="{
open: false,
search: '{{ $settings->instance_timezone ?: '' }}',
timezones: @js($timezones),
placeholder: '{{ $settings->instance_timezone ? 'Search timezone...' : 'Select Server Timezone' }}',
init() {
this.$watch('search', value => {
if (value === '') {
this.open = true;
}
})
}
}">
<div class="flex items-center mb-1">
<label for="settings.instance_timezone">Instance
Timezone</label>
<x-helper class="ml-2"
helper="Timezone for the Coolify instance. This is used for the update check and automatic update frequency." />
</div>
<div class="relative">
<div class="inline-flex items-center relative w-full">
<input wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" x-model="search"
@focus="open = true" @click.away="open = false" @input="open = true"
class="w-full input " :placeholder="placeholder"
wire:model.debounce.300ms="settings.instance_timezone">
<svg class="absolute right-0 w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
@click="open = true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9" />
</svg>
</div>
<div x-show="open"
class="absolute z-50 w-full mt-1 bg-white dark:bg-coolgray-100 border dark:border-coolgray-200 rounded-md shadow-lg max-h-60 overflow-auto scrollbar overflow-x-hidden">
<template
x-for="timezone in timezones.filter(tz => tz.toLowerCase().includes(search.toLowerCase()))"
:key="timezone">
<div @click="search = timezone; open = false; $wire.set('settings.instance_timezone', timezone)"
class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-coolgray-300 text-gray-800 dark:text-gray-200"
x-text="timezone"></div>
</template>
</div>
</div>
</div>
</div>
<h4 class="w-full pt-6">DNS Validation</h4>
<div class="md:w-96">
<x-forms.checkbox instantSave id="is_dns_validation_enabled" label="Enabled" />
</div>
<x-forms.input id="settings.custom_dns_servers" label="DNS Servers"
helper="DNS servers for validation FQDNs againts. A comma separated list of DNS servers."
helper="DNS servers to validate FQDNs against. A comma separated list of DNS servers."
placeholder="1.1.1.1,8.8.8.8" />
</div>

View File

@@ -14,13 +14,31 @@
</a>
@endif
@if ($applications->count() > 0)
<x-modal-confirmation disabled isErrorButton buttonTitle="Delete">
This source will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation
title="Confirm GitHub App Deletion?"
isErrorButton
buttonTitle="Delete"
submitAction="delete"
:actions="['The selected GitHub App will be permanently deleted.']"
confirmationText="{{ data_get($github_app, 'name') }}"
confirmationLabel="Please confirm the execution of the actions by entering the GitHub App Name below"
shortConfirmationLabel="GitHub App Name"
:confirmWithPassword="false"
step2ButtonText="Permanently Delete"
/>
@else
<x-modal-confirmation isErrorButton buttonTitle="Delete">
This source will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation
title="Confirm GitHub App Deletion?"
isErrorButton
buttonTitle="Delete"
submitAction="delete"
:actions="['The selected GitHub App will be permanently deleted.']"
confirmationLabel="Please confirm the execution of the actions by entering the GitHub App Name below"
shortConfirmationLabel="GitHub App Name"
confirmationText="{{ data_get($github_app, 'name') }}"
:confirmWithPassword="false"
step2ButtonText="Permanently Delete"
/>
@endif
</div>
</div>
@@ -160,9 +178,18 @@
<div class="flex items-center gap-2 pb-4">
<h1>GitHub App</h1>
<div class="flex gap-2">
<x-modal-confirmation isErrorButton buttonTitle="Delete">
This source will be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation
title="Confirm GitHub App Deletion?"
isErrorButton
buttonTitle="Delete"
submitAction="delete"
:actions="['The selected GitHub App will be permanently deleted.']"
confirmationText="{{ data_get($github_app, 'name') }}"
confirmationLabel="Please confirm the execution of the actions by entering the GitHub App Name below"
shortConfirmationLabel="GitHub App Name"
:confirmWithPassword="false"
step2ButtonText="Permanently Delete"
/>
</div>
</div>
<div class="mb-10 rounded alert-error">

View File

@@ -16,9 +16,13 @@
<x-forms.button wire:click="test_s3_connection">
Validate Connection
</x-forms.button>
<x-modal-confirmation isErrorButton buttonTitle="Delete">
This storage will be deleted. It is not reversible. Your data won't be touched!<br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Storage Deletion?" isErrorButton buttonTitle="Delete"
submitAction="delete({{ $storage->id }})" :actions="[
'The selected storage location will be permanently deleted from Coolify.',
'If the storage location is in use by any backup jobs those backup jobs will only store the backup locally on the server.',
]" confirmationText="{{ $storage->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Storage Name below"
shortConfirmationLabel="Storage Name" :confirmWithPassword="false" step2ButtonText="Permanently Delet" />
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="storage.name" />

View File

@@ -45,7 +45,7 @@
</div>
</div>
<div class="pt-4">
If you have any problem, please <a class="underline dark:text-white" href="{{ config('coolify.contact') }}"
If you have any problems, please <a class="underline dark:text-white" href="{{ config('coolify.contact') }}"
target="_blank">contact us.</a>
</div>
@endif

View File

@@ -20,9 +20,18 @@
<div class="w-[500px]">
<x-forms.input readonly label="Deploy Webhook URL" id="webhook" />
</div>
<x-modal-confirmation isHighlighted buttonTitle="Redeploy All" action="redeploy_all">
All resources will be redeployed.
</x-modal-confirmation>
<x-modal-confirmation
title="Redeploy all resources with this tag?"
isHighlighted
buttonTitle="Redeploy All"
submitAction="redeploy_all"
:actions="['All resources with this tag will be redeployed.', 'During redeploy resources will be temporarily unavailable.']"
confirmationText="{{ $oneTag->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Tag Name below"
shortConfirmationLabel="Tag Name"
:confirmWithPassword="false"
step2ButtonText="Redeploy All"
/>
</div>
<div class="grid grid-cols-1 gap-2 pt-4 lg:grid-cols-2 xl:grid-cols-3">
@foreach ($applications as $application)

View File

@@ -24,9 +24,18 @@
<div class="w-[500px]">
<x-forms.input readonly label="Deploy Webhook URL" id="webhook" />
</div>
<x-modal-confirmation isHighlighted buttonTitle="Redeploy All" action="redeploy_all">
All resources will be redeployed.
</x-modal-confirmation>
<x-modal-confirmation
title="Redeploy all resources with this tag?"
isHighlighted
buttonTitle="Redeploy All"
submitAction="redeploy_all"
:actions="['All resources with this tag will be redeployed.', 'During redeploy resources will be temporarily unavailable.']"
confirmationText="{{ $oneTag->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Tag Name below"
shortConfirmationLabel="Tag Name"
:confirmWithPassword="false"
step2ButtonText="Redeploy All"
/>
</div>
<div class="grid grid-cols-1 gap-2 pt-4 lg:grid-cols-2 xl:grid-cols-3">
@foreach ($applications as $application)

View File

@@ -15,14 +15,14 @@
<div>{{ $user->email }}</div>
<div class="flex-1"></div>
<div class="flex items-center justify-center gap-2 mx-4 text-xs font-bold ">
<x-modal-confirmation isErrorButton action="delete({{ $user->id }})" buttonTitle="Delete">
This will delete all resources (application, databases, services, configurations, servers,
private keys, tags, etc.) from Coolify and <span
class="font-bold text-red-500 dark:text-warning">from the server (if it's reachable)</span>.
<br> <br>
It is not reversible. <br><br>
<div class="font-bold text-red-500 dark:text-white">Think twice!</div>
</x-modal-confirmation>
<x-modal-confirmation title="Confirm User Deletion?" buttonTitle="Delete" isErrorButton
submitAction="delete({{ $user->id }})" :actions="[
'The selected user will be permanently deleted from Coolify and the database.',
'All resources (application, databases, services, configurations, servers, private keys, tags, etc.) related to this user will be deleted from Coolify and from the server (if the server is reachable).',
]"
confirmationText="{{ $user->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the User Name below"
shortConfirmationLabel="User Name" step3ButtonText="Permanently Delete" />
</div>
</div>
@empty

View File

@@ -31,9 +31,11 @@
@else
@if (currentTeam()->isEmpty())
<div class="pb-4">This will delete your team. Beware! There is no coming back!</div>
<x-modal-confirmation isErrorButton buttonTitle="Delete">
This team be deleted. It is not reversible. <br>Please think again.
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Team Deletion?" buttonTitle="Delete" isErrorButton
submitAction="delete({{ currentTeam()->id }})" :actions="['The current team will be permanently deleted from Coolify and the database.']"
confirmationText="{{ currentTeam()->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Team Name below"
shortConfirmationLabel="Team Name" :confirmWithPassword="false" step2ButtonText="Permanently Delete" />
@else
<div>
<div class="pb-4">You need to delete the following resources to be able to delete the team:</div>

View File

@@ -0,0 +1,34 @@
<div>
<x-slot:title>
Terminal | Coolify
</x-slot>
<h1>Terminal</h1>
<div class="flex gap-2 items-end subtitle">
<div>Execute commands on your servers and containers without leaving the browser.</div>
<x-helper
helper="If you're having trouble connecting to your server, make sure that the port is open.<br><br><a class='underline' href='https://coolify.io/docs/knowledge-base/server/firewall/#terminal' target='_blank'>Documentation</a>"></x-helper>
</div>
<div>
<form class="flex flex-col gap-2 justify-center xl:items-end xl:flex-row"
wire:submit="$dispatchSelf('connectToContainer')">
<x-forms.select id="server" required wire:model.live="selected_uuid">
@foreach ($servers as $server)
@if ($loop->first)
<option disabled value="default">Select a server or container</option>
@endif
<option value="{{ $server->uuid }}">{{ $server->name }}</option>
@foreach ($containers as $container)
@if ($container['server_uuid'] == $server->uuid)
<option value="{{ $container['uuid'] }}">
{{ $server->name }} -> {{ $container['name'] }}
</option>
@endif
@endforeach
@endforeach
</x-forms.select>
<x-forms.button type="submit">Connect</x-forms.button>
</form>
<livewire:project.shared.terminal />
</div>
</div>