Merge branch 'next' into fix-#2546-deletion-issues
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
@@ -209,27 +276,37 @@
|
||||
<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" />
|
||||
label="Name" id="remoteServerName" wire:model="remoteServerName" />
|
||||
<x-forms.input placeholder="Description, so others will know more about this."
|
||||
label="Description" id="remoteServerDescription" />
|
||||
label="Description" id="remoteServerDescription"
|
||||
wire:model="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>.
|
||||
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost"
|
||||
wire:model="remoteServerHost" />
|
||||
</div>
|
||||
<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 lg:flex-row">
|
||||
<x-forms.input placeholder="Port number of your server. Default is 22." label="Port"
|
||||
id="remoteServerPort" wire:model="remoteServerPort" />
|
||||
<div class="w-full">
|
||||
<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" />
|
||||
id="isCloudflareTunnel" label="Cloudflare Tunnel" wire:model="isCloudflareTunnel" />
|
||||
</div>
|
||||
<x-forms.button type="submit">Continue</x-forms.button>
|
||||
</form>
|
||||
@@ -329,5 +406,4 @@
|
||||
<livewire:help />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -169,12 +169,14 @@
|
||||
</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> --}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -263,6 +263,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 />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,34 +4,45 @@
|
||||
<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
|
||||
@@ -49,10 +60,9 @@
|
||||
step3ButtonText="Permanently Delete Backup"
|
||||
/>
|
||||
</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>
|
||||
@@ -61,5 +71,4 @@
|
||||
}
|
||||
</script>
|
||||
@endisset
|
||||
|
||||
</div>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -7,16 +7,21 @@
|
||||
</x-slot:content>
|
||||
</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">
|
||||
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}" href="{{ route('project.database.configuration', $parameters) }}">
|
||||
<nav
|
||||
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) }}">
|
||||
|
||||
<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' ||
|
||||
@@ -27,7 +32,7 @@
|
||||
</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
|
||||
title="Confirm Database Restart?"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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/_/redis'>https://hub.docker.com/_/redis</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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -617,6 +617,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"
|
||||
|
||||
@@ -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'"
|
||||
@@ -176,7 +172,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>
|
||||
@@ -199,9 +195,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>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<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">
|
||||
@@ -64,10 +66,38 @@
|
||||
step3ButtonText="Permanently Delete File"
|
||||
/>
|
||||
@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
|
||||
</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>
|
||||
|
||||
@@ -6,16 +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">
|
||||
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}" href="{{ route('project.service.configuration', $parameters) }}">
|
||||
<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">
|
||||
@@ -85,20 +90,23 @@
|
||||
</x-slot:button-title>
|
||||
</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">
|
||||
<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" />
|
||||
</svg>
|
||||
Force Cleanup Containers
|
||||
</button>
|
||||
<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" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</svg>
|
||||
Deploy
|
||||
</button>
|
||||
<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">
|
||||
<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" />
|
||||
</svg>
|
||||
Force Cleanup Containers
|
||||
</button>
|
||||
<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"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</svg>
|
||||
Deploy
|
||||
</button>
|
||||
@else
|
||||
<x-modal-confirmation
|
||||
title="Confirm Service Stopping?"
|
||||
|
||||
@@ -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 }}" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
@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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<div>
|
||||
<x-slot:title>
|
||||
{{ data_get_str($resource, 'name')->limit(10) }} > Scheduled Tasks | Coolify
|
||||
</x-slot>
|
||||
@if ($type === 'application')
|
||||
</x-slot>
|
||||
@if ($type === 'application')
|
||||
<h1>Scheduled Task</h1>
|
||||
<livewire:project.application.heading :application="$resource" />
|
||||
@elseif ($type === 'service')
|
||||
@elseif ($type === 'service')
|
||||
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" />
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<form wire:submit="submit" class="w-full">
|
||||
<div class="flex flex-col gap-2 pb-2">
|
||||
@@ -28,29 +28,26 @@
|
||||
:confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Delete Scheduled Task"
|
||||
/>
|
||||
</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>
|
||||
</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)" />
|
||||
</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 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 :task="$task" key="{{ $task->id }}" selectedKey="" :executions="$task->executions->take(20)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
240
resources/views/livewire/project/shared/terminal.blade.php
Normal file
240
resources/views/livewire/project/shared/terminal.blade.php
Normal 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>
|
||||
@@ -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>
|
||||
@@ -79,6 +79,48 @@
|
||||
<x-forms.input type="number" id="server.port" label="Port" required />
|
||||
</div>
|
||||
</div>
|
||||
<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 items-center relative 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 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-64 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('server.settings.server_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 class="w-64">
|
||||
@if ($server->isFunctional())
|
||||
@if (!$server->isLocalhost())
|
||||
@@ -140,31 +182,29 @@
|
||||
</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
|
||||
@@ -186,12 +226,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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
34
resources/views/livewire/terminal/index.blade.php
Normal file
34
resources/views/livewire/terminal/index.blade.php
Normal 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>
|
||||
Reference in New Issue
Block a user