mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 02:16:43 +00:00
Merge remote-tracking branch 'origin/next' into jean/port-exposes-improvement
This commit is contained in:
commit
07f381b88c
766 files changed
+43286
-12686
No files matched your search
@@ -34,10 +34,10 @@
|
||||
}
|
||||
}" x-init="// Initial scroll
|
||||
$nextTick(() => scrollToBottom());
|
||||
|
||||
|
||||
// Add scroll event listener
|
||||
$el.addEventListener('scroll', () => handleScroll());
|
||||
|
||||
|
||||
// Set up mutation observer to watch for content changes
|
||||
observer = new MutationObserver(() => {
|
||||
$nextTick(() => scrollToBottom());
|
||||
@@ -52,7 +52,7 @@
|
||||
'flex-1 min-h-0' => $fullHeight,
|
||||
'max-h-96' => !$fullHeight,
|
||||
])>
|
||||
<pre class="font-mono whitespace-pre-wrap" @if ($isPollingActive) wire:poll.1000ms="polling" @endif>{{ RunRemoteProcess::decodeOutput($activity) }}</pre>
|
||||
<pre class="font-logs whitespace-pre-wrap" @if ($isPollingActive) wire:poll.1000ms="polling" @endif>{{ RunRemoteProcess::decodeOutput($activity) }}</pre>
|
||||
</div>
|
||||
@else
|
||||
@if ($showWaiting)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<x-modal-input buttonTitle="Add" title="New Project">
|
||||
<x-slot:content>
|
||||
<button
|
||||
class="flex items-center justify-center size-4 text-white rounded hover:bg-coolgray-400 dark:hover:bg-coolgray-300 cursor-pointer">
|
||||
class="flex items-center justify-center size-4 text-black dark:text-white rounded hover:bg-coolgray-400 dark:hover:bg-coolgray-300 cursor-pointer">
|
||||
<svg class="size-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
@@ -81,7 +81,7 @@
|
||||
<x-modal-input buttonTitle="Add" title="New Server" :closeOutside="false">
|
||||
<x-slot:content>
|
||||
<button
|
||||
class="flex items-center justify-center size-4 text-white rounded hover:bg-coolgray-400 dark:hover:bg-coolgray-300 cursor-pointer">
|
||||
class="flex items-center justify-center size-4 text-black dark:text-white rounded hover:bg-coolgray-400 dark:hover:bg-coolgray-300 cursor-pointer">
|
||||
<svg class="size-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<div wire:poll.3000ms x-data="{
|
||||
expanded: @entangle('expanded'),
|
||||
reduceOpacity: @js($this->shouldReduceOpacity)
|
||||
}" class="fixed bottom-0 z-60 mb-4 left-0 lg:left-56 ml-4">
|
||||
}" class="fixed bottom-0 left-0 z-60 mb-4 ml-4 transition-[left] duration-200"
|
||||
:class="collapsed ? 'lg:left-16' : 'lg:left-56'">
|
||||
@if ($this->deploymentCount > 0)
|
||||
<div class="relative transition-opacity duration-200"
|
||||
:class="{ 'opacity-100': expanded || !reduceOpacity, 'opacity-60 hover:opacity-100': reduceOpacity && !expanded }">
|
||||
|
||||
@@ -14,31 +14,30 @@
|
||||
</div>
|
||||
<div class="subtitle">Network endpoints to deploy your resources.</div>
|
||||
<div class="grid gap-4 lg:grid-cols-2 -mt-1">
|
||||
@forelse ($servers as $server)
|
||||
@forelse ($server->destinations() as $destination)
|
||||
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
||||
<a class="coolbox group" {{ wireNavigate() }}
|
||||
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
||||
<div class="flex flex-col justify-center mx-6">
|
||||
<div class="box-title">{{ $destination->name }}</div>
|
||||
<div class="box-description">Server: {{ $destination->server->name }}</div>
|
||||
@forelse ($destinations as $destination)
|
||||
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
||||
<a class="coolbox group" {{ wireNavigate() }}
|
||||
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
||||
<div class="flex flex-col justify-center mx-6">
|
||||
<div class="box-title">{{ $destination->name }}</div>
|
||||
<div class="box-description">Server: {{ $destination->server->name }}</div>
|
||||
</div>
|
||||
</a>
|
||||
@endif
|
||||
@if ($destination->getMorphClass() === 'App\Models\SwarmDocker')
|
||||
<a class="coolbox group" {{ wireNavigate() }}
|
||||
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="box-title">
|
||||
{{ $destination->name }}
|
||||
<x-deprecated-badge />
|
||||
</div>
|
||||
</a>
|
||||
@endif
|
||||
@if ($destination->getMorphClass() === 'App\Models\SwarmDocker')
|
||||
<a class="coolbox group" {{ wireNavigate() }}
|
||||
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="box-title">{{ $destination->name }}</div>
|
||||
<div class="box-description">server: {{ $destination->server->name }}</div>
|
||||
</div>
|
||||
</a>
|
||||
@endif
|
||||
@empty
|
||||
<div>No destinations found.</div>
|
||||
@endforelse
|
||||
<div class="box-description">Server: {{ $destination->server->name }}</div>
|
||||
</div>
|
||||
</a>
|
||||
@endif
|
||||
@empty
|
||||
<div>No servers found.</div>
|
||||
<div>No destinations found.</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
@@ -16,7 +16,9 @@
|
||||
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
||||
<div class="subtitle ">A simple Docker network.</div>
|
||||
@else
|
||||
<div class="subtitle ">A swarm Docker network. WIP</div>
|
||||
<div class="subtitle flex items-center gap-2">A swarm Docker network.
|
||||
<x-deprecated-badge />
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$destination" id="name" label="Name" />
|
||||
|
||||
@@ -835,6 +835,20 @@
|
||||
<div class="font-medium text-neutral-900 dark:text-white truncate"
|
||||
x-text="item.name">
|
||||
</div>
|
||||
<template x-if="item.amd_only">
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs rounded-full bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200 shrink-0"
|
||||
title="This service only supports AMD64/x86_64 architecture">
|
||||
AMD only
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="item.arm_only">
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs rounded-full bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200 shrink-0"
|
||||
title="This service only supports ARM64/aarch64 architecture">
|
||||
ARM only
|
||||
</span>
|
||||
</template>
|
||||
<span
|
||||
class="text-xs text-neutral-500 dark:text-neutral-400 shrink-0"
|
||||
x-text="item.quickcommand"
|
||||
@@ -1123,4 +1137,4 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,7 +30,7 @@
|
||||
@endif
|
||||
</div>
|
||||
@if (!isCloud())
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="instantSave()" id="useInstanceEmailSettings"
|
||||
label="Use system wide (transactional) email settings" />
|
||||
</div>
|
||||
@@ -72,7 +72,7 @@
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||
<x-forms.input canGate="update" :canResource="$settings" required id="smtpHost" placeholder="smtp.mailgun.org" label="Host" />
|
||||
<x-forms.input canGate="update" :canResource="$settings" required id="smtpPort" placeholder="587" label="Port" />
|
||||
<x-forms.input canGate="update" :canResource="$settings" required id="smtpPort" type="number" placeholder="587" label="Port" />
|
||||
<x-forms.select canGate="update" :canResource="$settings" required id="smtpEncryption" label="Encryption">
|
||||
<option value="starttls">StartTLS</option>
|
||||
<option value="tls">TLS/SSL</option>
|
||||
@@ -82,7 +82,7 @@
|
||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||
<x-forms.input canGate="update" :canResource="$settings" id="smtpUsername" label="SMTP Username" />
|
||||
<x-forms.input canGate="update" :canResource="$settings" id="smtpPassword" type="password" label="SMTP Password" />
|
||||
<x-forms.input canGate="update" :canResource="$settings" id="smtpTimeout" helper="Timeout value for sending emails."
|
||||
<x-forms.input canGate="update" :canResource="$settings" id="smtpTimeout" type="number" helper="Timeout value for sending emails."
|
||||
label="Timeout" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<h3 class="text-lg font-medium mb-3">Deployments</h3>
|
||||
<div class="flex flex-col gap-1.5 pl-1">
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentSuccessTelegramNotifications"
|
||||
label="Deployment Success" />
|
||||
</div>
|
||||
@@ -49,7 +49,7 @@
|
||||
id="telegramNotificationsDeploymentSuccessThreadId" />
|
||||
</div>
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentFailureTelegramNotifications"
|
||||
label="Deployment Failure" />
|
||||
</div>
|
||||
@@ -57,7 +57,7 @@
|
||||
id="telegramNotificationsDeploymentFailureThreadId" />
|
||||
</div>
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="statusChangeTelegramNotifications"
|
||||
label="Container Status Changes"
|
||||
helper="Send a notification when a container status changes. It will send a notification for Stopped and Restarted events of a container." />
|
||||
@@ -71,7 +71,7 @@
|
||||
<h3 class="text-lg font-medium mb-3">Backups</h3>
|
||||
<div class="flex flex-col gap-1.5 pl-1">
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupSuccessTelegramNotifications"
|
||||
label="Backup Success" />
|
||||
</div>
|
||||
@@ -80,7 +80,7 @@
|
||||
</div>
|
||||
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupFailureTelegramNotifications"
|
||||
label="Backup Failure" />
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@
|
||||
<h3 class="text-lg font-medium mb-3">Scheduled Tasks</h3>
|
||||
<div class="flex flex-col gap-1.5 pl-1">
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskSuccessTelegramNotifications"
|
||||
label="Scheduled Task Success" />
|
||||
</div>
|
||||
@@ -103,7 +103,7 @@
|
||||
</div>
|
||||
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskFailureTelegramNotifications"
|
||||
label="Scheduled Task Failure" />
|
||||
</div>
|
||||
@@ -117,7 +117,7 @@
|
||||
<h3 class="text-lg font-medium mb-3">Server</h3>
|
||||
<div class="flex flex-col gap-1.5 pl-1">
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupSuccessTelegramNotifications"
|
||||
label="Docker Cleanup Success" />
|
||||
</div>
|
||||
@@ -126,7 +126,7 @@
|
||||
</div>
|
||||
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupFailureTelegramNotifications"
|
||||
label="Docker Cleanup Failure" />
|
||||
</div>
|
||||
@@ -135,7 +135,7 @@
|
||||
</div>
|
||||
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverDiskUsageTelegramNotifications"
|
||||
label="Server Disk Usage" />
|
||||
</div>
|
||||
@@ -144,7 +144,7 @@
|
||||
</div>
|
||||
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverReachableTelegramNotifications"
|
||||
label="Server Reachable" />
|
||||
</div>
|
||||
@@ -153,7 +153,7 @@
|
||||
</div>
|
||||
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverUnreachableTelegramNotifications"
|
||||
label="Server Unreachable" />
|
||||
</div>
|
||||
@@ -162,7 +162,7 @@
|
||||
</div>
|
||||
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverPatchTelegramNotifications"
|
||||
label="Server Patching" />
|
||||
</div>
|
||||
@@ -171,7 +171,7 @@
|
||||
</div>
|
||||
|
||||
<div class="pl-1 flex gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="traefikOutdatedTelegramNotifications"
|
||||
label="Traefik Proxy Outdated" />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<div>
|
||||
<x-slot:title>
|
||||
Appearance | Coolify
|
||||
</x-slot>
|
||||
<x-profile.navbar />
|
||||
|
||||
<div x-data="{
|
||||
theme: localStorage.getItem('theme') || 'dark',
|
||||
pageWidth: localStorage.getItem('pageWidth') || 'full',
|
||||
zoom: localStorage.getItem('zoom') || '100',
|
||||
init() {
|
||||
localStorage.setItem('theme', this.theme);
|
||||
localStorage.setItem('pageWidth', this.pageWidth);
|
||||
localStorage.setItem('zoom', this.zoom);
|
||||
this.applyTheme();
|
||||
},
|
||||
setTheme(type) {
|
||||
this.theme = type;
|
||||
localStorage.setItem('theme', type);
|
||||
this.applyTheme();
|
||||
},
|
||||
applyTheme() {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const isDark = this.theme === 'dark' || (this.theme === 'system' && prefersDark);
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
document.querySelector('meta[name=theme-color]')?.setAttribute('content', isDark ? '#101010' : '#ffffff');
|
||||
},
|
||||
setWidth(width) {
|
||||
this.pageWidth = width;
|
||||
localStorage.setItem('pageWidth', width);
|
||||
window.location.reload();
|
||||
},
|
||||
setZoom(value) {
|
||||
this.zoom = value;
|
||||
localStorage.setItem('zoom', value);
|
||||
window.location.reload();
|
||||
},
|
||||
}" class="flex max-w-2xl flex-col">
|
||||
<section class="space-y-1.5">
|
||||
<h2>Appearance</h2>
|
||||
<div>Choose how Coolify looks in this browser.</div>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<button type="button" @click="setTheme('light')" aria-label="Use light theme"
|
||||
class="flex items-center gap-2 rounded-sm border border-neutral-300 bg-white px-2 py-1 text-left text-sm hover:bg-neutral-100 focus-visible:ring-2 focus-visible:ring-coollabs dark:border-coolgray-300 dark:bg-coolgray-100 dark:hover:bg-coolgray-200 dark:focus-visible:ring-warning"
|
||||
:class="theme === 'light' && 'border-coollabs text-coollabs dark:border-warning dark:text-warning'">
|
||||
<svg class="size-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<span>Light</span>
|
||||
</button>
|
||||
<button type="button" @click="setTheme('system')" aria-label="Use system theme"
|
||||
class="flex items-center gap-2 rounded-sm border border-neutral-300 bg-white px-2 py-1 text-left text-sm hover:bg-neutral-100 focus-visible:ring-2 focus-visible:ring-coollabs dark:border-coolgray-300 dark:bg-coolgray-100 dark:hover:bg-coolgray-200 dark:focus-visible:ring-warning"
|
||||
:class="theme === 'system' && 'border-coollabs text-coollabs dark:border-warning dark:text-warning'">
|
||||
<svg class="size-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>System</span>
|
||||
</button>
|
||||
<button type="button" @click="setTheme('dark')" aria-label="Use dark theme"
|
||||
class="flex items-center gap-2 rounded-sm border border-neutral-300 bg-white px-2 py-1 text-left text-sm hover:bg-neutral-100 focus-visible:ring-2 focus-visible:ring-coollabs dark:border-coolgray-300 dark:bg-coolgray-100 dark:hover:bg-coolgray-200 dark:focus-visible:ring-warning"
|
||||
:class="theme === 'dark' && 'border-coollabs text-coollabs dark:border-warning dark:text-warning'">
|
||||
<svg class="size-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||
</svg>
|
||||
<span>Dark</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="space-y-1.5">
|
||||
<h2>Width</h2>
|
||||
<div>Choose the maximum page width for this browser.</div>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<button type="button" @click="setWidth('center')" aria-label="Use centered width"
|
||||
class="flex items-center gap-2 rounded-sm border border-neutral-300 bg-white px-2 py-1 text-sm hover:bg-neutral-100 focus-visible:ring-2 focus-visible:ring-coollabs dark:border-coolgray-300 dark:bg-coolgray-100 dark:hover:bg-coolgray-200 dark:focus-visible:ring-warning"
|
||||
:class="pageWidth === 'center' && 'border-coollabs text-coollabs dark:border-warning dark:text-warning'">
|
||||
<svg class="size-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
|
||||
</svg>
|
||||
Center
|
||||
</button>
|
||||
<button type="button" @click="setWidth('full')" aria-label="Use full width"
|
||||
class="flex items-center gap-2 rounded-sm border border-neutral-300 bg-white px-2 py-1 text-sm hover:bg-neutral-100 focus-visible:ring-2 focus-visible:ring-coollabs dark:border-coolgray-300 dark:bg-coolgray-100 dark:hover:bg-coolgray-200 dark:focus-visible:ring-warning"
|
||||
:class="pageWidth === 'full' && 'border-coollabs text-coollabs dark:border-warning dark:text-warning'">
|
||||
<svg class="size-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
Full
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="space-y-1.5">
|
||||
<h2>Zoom</h2>
|
||||
<div>Choose interface density for this browser.</div>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<button type="button" @click="setZoom('100')" aria-label="Use 100 percent zoom"
|
||||
class="flex items-center gap-2 rounded-sm border border-neutral-300 bg-white px-2 py-1 text-sm hover:bg-neutral-100 focus-visible:ring-2 focus-visible:ring-coollabs dark:border-coolgray-300 dark:bg-coolgray-100 dark:hover:bg-coolgray-200 dark:focus-visible:ring-warning"
|
||||
:class="zoom === '100' && 'border-coollabs text-coollabs dark:border-warning dark:text-warning'">
|
||||
<svg class="size-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
100%
|
||||
</button>
|
||||
<button type="button" @click="setZoom('90')" aria-label="Use 90 percent zoom"
|
||||
class="flex items-center gap-2 rounded-sm border border-neutral-300 bg-white px-2 py-1 text-sm hover:bg-neutral-100 focus-visible:ring-2 focus-visible:ring-coollabs dark:border-coolgray-300 dark:bg-coolgray-100 dark:hover:bg-coolgray-200 dark:focus-visible:ring-warning"
|
||||
:class="zoom === '90' && 'border-coollabs text-coollabs dark:border-warning dark:text-warning'">
|
||||
<svg class="size-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 10h4v4h-4v-4z" />
|
||||
</svg>
|
||||
90%
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,8 +2,7 @@
|
||||
<x-slot:title>
|
||||
Profile | Coolify
|
||||
</x-slot>
|
||||
<h1>Profile</h1>
|
||||
<div class="subtitle -mt-2">Your user profile settings.</div>
|
||||
<x-profile.navbar />
|
||||
<form wire:submit='submit' class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>General</h2>
|
||||
|
||||
@@ -5,19 +5,7 @@
|
||||
</div>
|
||||
<div>Advanced configuration for your application.</div>
|
||||
<div class="flex flex-col gap-1 pt-4">
|
||||
<h3>General</h3>
|
||||
@if ($application->git_based())
|
||||
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
|
||||
id="isAutoDeployEnabled" label="Auto Deploy" canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox
|
||||
helper="Allow to automatically deploy Preview Deployments for all opened PR's.<br><br>Closing a PR will delete Preview Deployments."
|
||||
instantSave id="isPreviewDeploymentsEnabled" label="Preview Deployments" canGate="update"
|
||||
:canResource="$application" />
|
||||
<x-forms.checkbox
|
||||
helper="When enabled, anyone can trigger PR deployments. When disabled, only repository members, collaborators, and contributors can trigger PR deployments."
|
||||
instantSave id="isPrDeploymentsPublicEnabled" label="Allow Public PR Deployments" canGate="update"
|
||||
:canResource="$application" :disabled="!$isPreviewDeploymentsEnabled" />
|
||||
@endif
|
||||
<h3>Build</h3>
|
||||
<x-forms.checkbox helper="Disable Docker build cache on every deployment." instantSave
|
||||
id="disableBuildCache" label="Disable Build Cache" canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox
|
||||
@@ -29,6 +17,55 @@
|
||||
instantSave id="includeSourceCommitInBuild" label="Include Source Commit in Build" canGate="update"
|
||||
:canResource="$application" />
|
||||
|
||||
<h3 class="pt-4">Container</h3>
|
||||
<x-forms.checkbox
|
||||
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||
instantSave id="isConsistentContainerNameEnabled" label="Consistent Container Names" canGate="update"
|
||||
:canResource="$application" />
|
||||
@if ($isConsistentContainerNameEnabled === false)
|
||||
<form class="flex items-end gap-2 " wire:submit.prevent='saveCustomName'>
|
||||
<x-forms.input
|
||||
helper="You can add a custom name for your container.<br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||
instantSave id="customInternalName" label="Custom Container Name" canGate="update"
|
||||
:canResource="$application" />
|
||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if ($application->git_based())
|
||||
<h3 class="pt-4">Deployment</h3>
|
||||
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
|
||||
id="isAutoDeployEnabled" label="Auto Deploy" canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox
|
||||
helper="Allow to automatically deploy Preview Deployments for all opened PR's.<br><br>Closing a PR will delete Preview Deployments."
|
||||
instantSave id="isPreviewDeploymentsEnabled" label="Preview Deployments" canGate="update"
|
||||
:canResource="$application" />
|
||||
<x-forms.checkbox
|
||||
helper="When enabled, anyone can trigger PR deployments. When disabled, fork PRs are blocked and only repository owners, members, and collaborators can trigger PR deployments."
|
||||
instantSave id="isPrDeploymentsPublicEnabled" label="Allow Public PR Deployments" canGate="update"
|
||||
:canResource="$application" :disabled="!$isPreviewDeploymentsEnabled" />
|
||||
|
||||
<h3 class="pt-4">Git</h3>
|
||||
<x-forms.checkbox instantSave id="isGitSubmodulesEnabled" label="Submodules"
|
||||
helper="Allow Git Submodules during build process." canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox instantSave id="isGitLfsEnabled" label="LFS"
|
||||
helper="Allow Git LFS during build process." canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox instantSave id="isGitShallowCloneEnabled" label="Shallow Clone"
|
||||
helper="Use shallow cloning (--depth=1) to speed up deployments by only fetching the latest commit history. This reduces clone time and resource usage, especially for large repositories."
|
||||
canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
<h3 class="pt-4">Docker Compose</h3>
|
||||
<x-forms.checkbox instantSave id="isRawComposeDeploymentEnabled" label="Raw Compose Deployment"
|
||||
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>"
|
||||
canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox instantSave id="isConnectToDockerNetworkEnabled" label="Connect To Predefined Network"
|
||||
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>."
|
||||
canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
|
||||
<h3 class="pt-4">Proxy</h3>
|
||||
@if ($application->settings->is_container_label_readonly_enabled)
|
||||
<x-forms.checkbox
|
||||
helper="Your application will be available only on https if your domain starts with https://..."
|
||||
@@ -49,45 +86,24 @@
|
||||
helper="Readonly labels are disabled. You need to set the labels in the labels section." disabled
|
||||
instantSave id="isStripprefixEnabled" label="Strip Prefixes" canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
<h3>Docker Compose</h3>
|
||||
<x-forms.checkbox instantSave id="isRawComposeDeploymentEnabled" label="Raw Compose Deployment"
|
||||
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>"
|
||||
canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
<h3 class="pt-4">Container Names</h3>
|
||||
<x-forms.checkbox
|
||||
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||
instantSave id="isConsistentContainerNameEnabled" label="Consistent Container Names" canGate="update"
|
||||
:canResource="$application" />
|
||||
@if ($isConsistentContainerNameEnabled === false)
|
||||
<form class="flex items-end gap-2 " wire:submit.prevent='saveCustomName'>
|
||||
<x-forms.input
|
||||
helper="You can add a custom name for your container.<br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||
instantSave id="customInternalName" label="Custom Container Name" canGate="update"
|
||||
:canResource="$application" />
|
||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||
</form>
|
||||
@endif
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
<h3 class="pt-4">Network</h3>
|
||||
<x-forms.checkbox instantSave id="isConnectToDockerNetworkEnabled" label="Connect To Predefined Network"
|
||||
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>."
|
||||
canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
<h3 class="pt-4">Operations</h3>
|
||||
<form class="flex items-end gap-2" wire:submit.prevent='saveStopGracePeriod'>
|
||||
<x-forms.input
|
||||
type="number"
|
||||
id="stopGracePeriod"
|
||||
label="Stop Grace Period (seconds)"
|
||||
placeholder="{{ DEFAULT_STOP_GRACE_PERIOD_SECONDS }}"
|
||||
helper="How long to wait for graceful shutdown during rolling updates, manual stops, and restarts. Applies to all containers for this application. Default: {{ DEFAULT_STOP_GRACE_PERIOD_SECONDS }} seconds. Range: {{ MIN_STOP_GRACE_PERIOD_SECONDS }}-{{ MAX_STOP_GRACE_PERIOD_SECONDS }} seconds (1 hour)."
|
||||
min="{{ MIN_STOP_GRACE_PERIOD_SECONDS }}"
|
||||
max="{{ MAX_STOP_GRACE_PERIOD_SECONDS }}"
|
||||
canGate="update"
|
||||
:canResource="$application"
|
||||
/>
|
||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||
</form>
|
||||
<h3 class="pt-4">Logs</h3>
|
||||
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
|
||||
instantSave id="isLogDrainEnabled" label="Drain Logs" canGate="update" :canResource="$application" />
|
||||
@if ($application->git_based())
|
||||
<h3>Git</h3>
|
||||
<x-forms.checkbox instantSave id="isGitSubmodulesEnabled" label="Submodules"
|
||||
helper="Allow Git Submodules during build process." canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox instantSave id="isGitLfsEnabled" label="LFS"
|
||||
helper="Allow Git LFS during build process." canGate="update" :canResource="$application" />
|
||||
<x-forms.checkbox instantSave id="isGitShallowCloneEnabled" label="Shallow Clone"
|
||||
helper="Use shallow cloning (--depth=1) to speed up deployments by only fetching the latest commit history. This reduces clone time and resource usage, especially for large repositories."
|
||||
canGate="update" :canResource="$application" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
href="{{ route('project.application.advanced', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Advanced</span></a>
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Swarm Configuration</span></a>
|
||||
href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Swarm</span>
|
||||
</a>
|
||||
@endif
|
||||
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.application.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Environment Variables</span></a>
|
||||
@@ -26,27 +27,13 @@
|
||||
@endif
|
||||
<a class="sub-menu-item flex items-center gap-2" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.application.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Servers</span>
|
||||
@if ($application->server_status == false)
|
||||
<span title="One or more servers are unreachable or misconfigured.">
|
||||
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
|
||||
</svg>
|
||||
</span>
|
||||
@elseif ($application->additional_servers()->exists() && str($application->status)->contains('degraded'))
|
||||
<span title="Application is in degraded state across multiple servers.">
|
||||
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
|
||||
</svg>
|
||||
</span>
|
||||
@endif
|
||||
<livewire:project.application.server-status-badge :application="$application" />
|
||||
</a>
|
||||
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
<a @class(['sub-menu-item', 'menu-item-active' => str($currentRoute)->startsWith('project.application.scheduled-tasks')]) {{ wireNavigate() }}
|
||||
href="{{ route('project.application.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Scheduled Tasks</span></a>
|
||||
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.application.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Webhooks</span></a>
|
||||
@if ($application->git_based())
|
||||
@if ($application->git_based() || $application->build_pack === 'dockerimage')
|
||||
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.application.preview-deployments', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"><span class="menu-item-label">Preview Deployments</span></a>
|
||||
@endif
|
||||
@@ -84,6 +71,8 @@
|
||||
<livewire:project.shared.destination :resource="$application" />
|
||||
@elseif ($currentRoute === 'project.application.scheduled-tasks.show')
|
||||
<livewire:project.shared.scheduled-task.all :resource="$application" />
|
||||
@elseif ($currentRoute === 'project.application.scheduled-tasks')
|
||||
<livewire:project.shared.scheduled-task.show />
|
||||
@elseif ($currentRoute === 'project.application.webhooks')
|
||||
<livewire:project.shared.webhooks :resource="$application" />
|
||||
@elseif ($currentRoute === 'project.application.preview-deployments')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div>
|
||||
<div class="flex h-[calc(100vh-10rem)] min-h-[50rem] flex-col overflow-hidden">
|
||||
<x-slot:title>
|
||||
{{ data_get_str($application, 'name')->limit(10) }} > Deployment | Coolify
|
||||
</x-slot>
|
||||
@@ -9,6 +9,13 @@
|
||||
fullscreen: @entangle('fullscreen'),
|
||||
alwaysScroll: {{ $isKeepAliveOn ? 'true' : 'false' }},
|
||||
rafId: null,
|
||||
scrollTimeout: null,
|
||||
scrollDebounce: null,
|
||||
isScrolling: false,
|
||||
destroyed: false,
|
||||
morphUpdatedCleanup: null,
|
||||
deploymentFinishedCleanup: null,
|
||||
lastTouchY: 0,
|
||||
showTimestamps: true,
|
||||
searchQuery: '',
|
||||
matchCount: 0,
|
||||
@@ -17,17 +24,76 @@
|
||||
this.fullscreen = !this.fullscreen;
|
||||
},
|
||||
scrollToBottom() {
|
||||
const logsContainer = document.getElementById('logsContainer');
|
||||
if (this.destroyed) return;
|
||||
const logsContainer = this.$root.querySelector('#logsContainer');
|
||||
if (logsContainer) {
|
||||
this.isScrolling = true;
|
||||
logsContainer.scrollTop = logsContainer.scrollHeight;
|
||||
requestAnimationFrame(() => { this.isScrolling = false; });
|
||||
}
|
||||
},
|
||||
scheduleScroll() {
|
||||
cancelScrollLoop() {
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.rafId = null;
|
||||
}
|
||||
if (this.scrollTimeout) {
|
||||
clearTimeout(this.scrollTimeout);
|
||||
this.scrollTimeout = null;
|
||||
}
|
||||
if (this.scrollDebounce) {
|
||||
clearTimeout(this.scrollDebounce);
|
||||
this.scrollDebounce = null;
|
||||
}
|
||||
},
|
||||
disableFollow() {
|
||||
if (!this.alwaysScroll) return;
|
||||
this.alwaysScroll = false;
|
||||
this.cancelScrollLoop();
|
||||
},
|
||||
handleWheel(event) {
|
||||
if (this.alwaysScroll && event.deltaY < 0) {
|
||||
this.disableFollow();
|
||||
}
|
||||
},
|
||||
handleTouchStart(event) {
|
||||
this.lastTouchY = event.touches[0].clientY;
|
||||
},
|
||||
handleTouchMove(event) {
|
||||
if (!this.alwaysScroll) return;
|
||||
const currentY = event.touches[0].clientY;
|
||||
if (currentY > this.lastTouchY) {
|
||||
this.disableFollow();
|
||||
}
|
||||
this.lastTouchY = currentY;
|
||||
},
|
||||
handleKeyScroll(event) {
|
||||
if (!this.alwaysScroll) return;
|
||||
const upKeys = ['ArrowUp', 'PageUp', 'Home'];
|
||||
if (upKeys.includes(event.key)) {
|
||||
this.disableFollow();
|
||||
}
|
||||
},
|
||||
handleScroll(event) {
|
||||
if (this.isScrolling || this.destroyed) return;
|
||||
const el = event.target;
|
||||
clearTimeout(this.scrollDebounce);
|
||||
this.scrollDebounce = setTimeout(() => {
|
||||
if (this.destroyed) return;
|
||||
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
||||
if (!this.alwaysScroll && distanceFromBottom <= 10) {
|
||||
this.alwaysScroll = true;
|
||||
this.scheduleScroll();
|
||||
}
|
||||
}, 150);
|
||||
},
|
||||
scheduleScroll() {
|
||||
if (!this.alwaysScroll || this.destroyed) return;
|
||||
this.rafId = requestAnimationFrame(() => {
|
||||
if (!this.alwaysScroll || this.destroyed) return;
|
||||
this.scrollToBottom();
|
||||
if (this.alwaysScroll) {
|
||||
setTimeout(() => this.scheduleScroll(), 250);
|
||||
if (this.alwaysScroll && !this.destroyed) {
|
||||
this.scrollTimeout = setTimeout(() => this.scheduleScroll(), 250);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -36,10 +102,7 @@
|
||||
if (this.alwaysScroll) {
|
||||
this.scheduleScroll();
|
||||
} else {
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.rafId = null;
|
||||
}
|
||||
this.cancelScrollLoop();
|
||||
}
|
||||
},
|
||||
hasActiveLogSelection() {
|
||||
@@ -107,9 +170,9 @@
|
||||
|
||||
this.matchCount = query ? count : 0;
|
||||
},
|
||||
downloadLogs() {
|
||||
collectVisibleLogs() {
|
||||
const logs = document.getElementById('logs');
|
||||
if (!logs) return;
|
||||
if (!logs) return '';
|
||||
const visibleLines = logs.querySelectorAll('[data-log-line]:not(.hidden)');
|
||||
let content = '';
|
||||
visibleLines.forEach(line => {
|
||||
@@ -118,6 +181,17 @@
|
||||
content += text + String.fromCharCode(10);
|
||||
}
|
||||
});
|
||||
return content;
|
||||
},
|
||||
copyLogs() {
|
||||
const content = this.collectVisibleLogs();
|
||||
if (!content) return;
|
||||
navigator.clipboard.writeText(content);
|
||||
Livewire.dispatch('success', ['Logs copied to clipboard.']);
|
||||
},
|
||||
downloadLogs() {
|
||||
const content = this.collectVisibleLogs();
|
||||
if (!content) return;
|
||||
const blob = new Blob([content], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
@@ -130,10 +204,7 @@
|
||||
stopScroll() {
|
||||
this.scrollToBottom();
|
||||
this.alwaysScroll = false;
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.rafId = null;
|
||||
}
|
||||
this.cancelScrollLoop();
|
||||
},
|
||||
init() {
|
||||
// Watch search query changes
|
||||
@@ -141,21 +212,26 @@
|
||||
this.applySearch();
|
||||
});
|
||||
|
||||
// Apply search after Livewire updates
|
||||
Livewire.hook('morph.updated', ({ el }) => {
|
||||
if (el.id === 'logs') {
|
||||
this.$nextTick(() => {
|
||||
this.applySearch();
|
||||
if (this.alwaysScroll) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Apply search after Livewire updates.
|
||||
// Livewire.hook() returns an unregister fn; keep it for destroy().
|
||||
this.morphUpdatedCleanup = Livewire.hook('morph.updated', ({ el }) => {
|
||||
if (this.destroyed) return;
|
||||
if (el.id !== 'logs' || !this.$root.contains(el)) return;
|
||||
this.$nextTick(() => {
|
||||
if (this.destroyed) return;
|
||||
this.applySearch();
|
||||
if (this.alwaysScroll) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Stop auto-scroll when deployment finishes
|
||||
Livewire.on('deploymentFinished', () => {
|
||||
// Stop auto-scroll when deployment finishes.
|
||||
// Livewire.on() returns an unregister fn; keep it for destroy().
|
||||
this.deploymentFinishedCleanup = Livewire.on('deploymentFinished', () => {
|
||||
if (this.destroyed) return;
|
||||
setTimeout(() => {
|
||||
if (this.destroyed) return;
|
||||
this.stopScroll();
|
||||
}, 500);
|
||||
});
|
||||
@@ -164,14 +240,32 @@
|
||||
if (this.alwaysScroll) {
|
||||
this.scheduleScroll();
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
// Runs when Alpine tears the component down (wire:navigate away).
|
||||
this.destroyed = true;
|
||||
this.alwaysScroll = false;
|
||||
this.cancelScrollLoop();
|
||||
if (this.scrollDebounce) {
|
||||
clearTimeout(this.scrollDebounce);
|
||||
this.scrollDebounce = null;
|
||||
}
|
||||
if (typeof this.morphUpdatedCleanup === 'function') {
|
||||
this.morphUpdatedCleanup();
|
||||
this.morphUpdatedCleanup = null;
|
||||
}
|
||||
if (typeof this.deploymentFinishedCleanup === 'function') {
|
||||
this.deploymentFinishedCleanup();
|
||||
this.deploymentFinishedCleanup = null;
|
||||
}
|
||||
}
|
||||
}">
|
||||
}" class="flex flex-1 min-h-0 flex-col overflow-hidden">
|
||||
<livewire:project.application.deployment-navbar
|
||||
:application_deployment_queue="$application_deployment_queue" />
|
||||
<div id="screen" :class="fullscreen ? 'fullscreen flex flex-col' : 'mt-4 relative'">
|
||||
<div id="screen" :class="fullscreen ? 'fullscreen flex flex-col' : 'mt-4 flex flex-1 min-h-0 flex-col overflow-hidden'">
|
||||
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
|
||||
class="flex flex-col w-full bg-white dark:text-white dark:bg-coolgray-100 dark:border-coolgray-300"
|
||||
:class="fullscreen ? 'h-full' : 'border border-dotted rounded-sm'">
|
||||
class="flex min-h-0 flex-col w-full overflow-hidden bg-white dark:text-white dark:bg-coolgray-100 dark:border-coolgray-300"
|
||||
:class="fullscreen ? 'h-full' : 'flex-1 border border-dotted rounded-sm'">
|
||||
<div
|
||||
class="flex flex-wrap items-center justify-between gap-2 px-4 py-2 border-b dark:border-coolgray-300 border-neutral-200 shrink-0">
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -210,12 +304,7 @@
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-1">
|
||||
<button
|
||||
x-on:click="
|
||||
$wire.copyLogs().then(logs => {
|
||||
navigator.clipboard.writeText(logs);
|
||||
Livewire.dispatch('success', ['Logs copied to clipboard.']);
|
||||
});
|
||||
"
|
||||
x-on:click="copyLogs()"
|
||||
title="Copy Logs"
|
||||
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
@@ -327,10 +416,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="logsContainer"
|
||||
class="flex flex-col overflow-y-auto p-2 px-4 min-h-4 scrollbar"
|
||||
:class="fullscreen ? 'flex-1' : 'max-h-[30rem]'">
|
||||
<div id="logs" class="flex flex-col font-mono">
|
||||
<div id="logsContainer" @scroll="handleScroll" @wheel="handleWheel"
|
||||
@touchstart="handleTouchStart" @touchmove="handleTouchMove" @keydown="handleKeyScroll" tabindex="0"
|
||||
class="flex min-h-40 flex-1 flex-col overflow-y-auto p-2 px-4 scrollbar">
|
||||
<div id="logs" class="flex flex-col font-logs">
|
||||
<div x-show="searchQuery.trim() && matchCount === 0"
|
||||
class="text-gray-500 dark:text-gray-400 py-2">
|
||||
No matches found.
|
||||
@@ -356,11 +445,11 @@
|
||||
])>{{ $lineContent }}</span>
|
||||
</div>
|
||||
@empty
|
||||
<span class="font-mono text-neutral-400 mb-2">No logs yet.</span>
|
||||
<span class="font-logs text-neutral-400 mb-2">No logs yet.</span>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -12,6 +12,9 @@
|
||||
<div>{{ $application->compose_parsing_version }}</div>
|
||||
@endif
|
||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||
<x-modal-input title="Resource Details" buttonTitle="Details">
|
||||
<livewire:project.shared.resource-details :resource="$application" />
|
||||
</x-modal-input>
|
||||
@if ($buildPack === 'dockercompose')
|
||||
<x-forms.button canGate="update" :canResource="$application" wire:target='initLoadingCompose'
|
||||
x-on:click="$wire.dispatch('loadCompose', false)">
|
||||
@@ -32,6 +35,7 @@
|
||||
<x-forms.select x-bind:disabled="shouldDisable()" wire:model.live="buildPack" label="Build Pack"
|
||||
required>
|
||||
<option value="nixpacks">Nixpacks</option>
|
||||
<option value="railpack">Railpack (Beta)</option>
|
||||
<option value="static">Static</option>
|
||||
<option value="dockerfile">Dockerfile</option>
|
||||
<option value="dockercompose">Docker Compose</option>
|
||||
@@ -60,7 +64,7 @@
|
||||
@if (!isDatabaseImage(data_get($service, 'image')))
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.input
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."
|
||||
label="Domains for {{ $serviceName }}"
|
||||
id="parsedServiceDomains.{{ str($serviceName)->replace('-', '_')->replace('.', '_') }}.domain"
|
||||
x-bind:disabled="shouldDisable()"></x-forms.input>
|
||||
@@ -114,7 +118,7 @@
|
||||
x-bind:disabled="!canUpdate" />
|
||||
@else
|
||||
<x-forms.input placeholder="https://coolify.io" wire:model="fqdn" label="Domains"
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."
|
||||
x-bind:disabled="!canUpdate" />
|
||||
@can('update', $application)
|
||||
<x-forms.button wire:click="getWildcardDomain">Generate Domain
|
||||
@@ -226,20 +230,24 @@
|
||||
id="customDockerRunOptions" label="Custom Docker Options" x-bind:disabled="!canUpdate" />
|
||||
@else
|
||||
@if ($application->could_set_build_commands())
|
||||
@if ($buildPack === 'nixpacks')
|
||||
@if ($buildPack === 'nixpacks' || $buildPack === 'railpack')
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
|
||||
<x-forms.input helper="If you modify this, you probably need to have a {{ $buildPack === 'railpack' ? 'railpack.json' : 'nixpacks.toml' }}"
|
||||
id="installCommand" label="Install Command" x-bind:disabled="!canUpdate" />
|
||||
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
|
||||
<x-forms.input helper="If you modify this, you probably need to have a {{ $buildPack === 'railpack' ? 'railpack.json' : 'nixpacks.toml' }}"
|
||||
id="buildCommand" label="Build Command" x-bind:disabled="!canUpdate" />
|
||||
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
|
||||
<x-forms.input helper="If you modify this, you probably need to have a {{ $buildPack === 'railpack' ? 'railpack.json' : 'nixpacks.toml' }}"
|
||||
id="startCommand" label="Start Command" x-bind:disabled="!canUpdate" />
|
||||
</div>
|
||||
<div class="pt-1 text-xs">Nixpacks will detect the required configuration
|
||||
automatically.
|
||||
@if ($buildPack === 'nixpacks')
|
||||
<div class="pt-1 text-xs">
|
||||
|
||||
<span class="font-medium">Nixpacks</span>
|
||||
will detect the required configuration automatically.
|
||||
<a class="underline" href="https://coolify.io/docs/applications/">Framework
|
||||
Specific Docs</a>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@endif
|
||||
@@ -276,7 +284,7 @@
|
||||
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($baseDirectory . $dockerComposeLocation, '/') }}</span>"
|
||||
x-model="composeLocation" @blur="normalizeComposeLocation()" />
|
||||
</div>
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox instantSave id="isPreserveRepositoryEnabled"
|
||||
label="Preserve Repository During Deployment"
|
||||
helper="Git repository (based on the base directory settings) will be copied to the deployment directory."
|
||||
@@ -382,7 +390,7 @@
|
||||
x-bind:disabled="!canUpdate" />
|
||||
|
||||
@if ($buildPack !== 'dockercompose')
|
||||
<div class="pt-2 w-96">
|
||||
<div class="pt-2 w-full sm:w-96">
|
||||
<x-forms.checkbox
|
||||
helper="Use a build server to build your application. You can configure your build server in the Server settings. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
|
||||
instantSave id="isBuildServerEnabled" label="Use a Build Server?"
|
||||
@@ -422,7 +430,7 @@
|
||||
monacoEditorLanguage="yaml" useMonacoEditor />
|
||||
</div>
|
||||
@endif
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox label="Escape special characters in labels?"
|
||||
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
||||
id="isContainerLabelEscapeEnabled" instantSave
|
||||
@@ -528,7 +536,7 @@
|
||||
|
||||
<h3 class="pt-8">HTTP Basic Authentication</h3>
|
||||
<div>
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox helper="This will add the proper proxy labels to the container." instantSave
|
||||
label="Enable" id="isHttpBasicAuthEnabled" x-bind:disabled="!canUpdate" />
|
||||
</div>
|
||||
@@ -550,7 +558,7 @@
|
||||
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"
|
||||
monacoEditorLanguage="ini" useMonacoEditor x-bind:disabled="!canUpdate"></x-forms.textarea>
|
||||
@endif
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox label="Readonly labels"
|
||||
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenerate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenerate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
|
||||
id="isContainerLabelReadonlyEnabled" instantSave
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<nav wire:poll.10000ms="checkStatus" class="pb-6">
|
||||
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :title="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
|
||||
<div class="navbar-main">
|
||||
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
||||
<a class="{{ request()->routeIs('project.application.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
<nav
|
||||
class="scrollbar flex min-h-10 w-full flex-nowrap items-center gap-6 overflow-x-scroll overflow-y-hidden pb-1 whitespace-nowrap md:w-auto md:overflow-visible">
|
||||
<a class="shrink-0 {{ request()->routeIs('project.application.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
href="{{ route('project.application.configuration', $parameters) }}">
|
||||
Configuration
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.application.deployment.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
<a class="shrink-0 {{ request()->routeIs('project.application.deployment.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
href="{{ route('project.application.deployment.index', $parameters) }}">
|
||||
Deployments
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.application.logs') ? 'dark:text-white' : '' }}"
|
||||
<a class="shrink-0 {{ request()->routeIs('project.application.logs') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('project.application.logs', $parameters) }}">
|
||||
<div class="flex items-center gap-1">
|
||||
Logs
|
||||
@@ -23,100 +24,160 @@
|
||||
</a>
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
@can('canAccessTerminal')
|
||||
<a class="{{ request()->routeIs('project.application.command') ? 'dark:text-white' : '' }}"
|
||||
<a class="shrink-0 {{ request()->routeIs('project.application.command') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('project.application.command', $parameters) }}">
|
||||
Terminal
|
||||
</a>
|
||||
@endcan
|
||||
@endif
|
||||
<x-applications.links :application="$application" />
|
||||
<div class="shrink-0">
|
||||
<x-applications.links :application="$application" />
|
||||
</div>
|
||||
</nav>
|
||||
<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
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<div>
|
||||
<x-applications.advanced :application="$application" />
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@if (!str($application->status)->startsWith('exited'))
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<x-forms.button title="With rolling update if possible" wire:click='deploy'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-orange-400"
|
||||
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
|
||||
</path>
|
||||
<path d="M7.05 11.038v-3.988"></path>
|
||||
</svg>
|
||||
Redeploy
|
||||
</x-forms.button>
|
||||
<div class="md:hidden">
|
||||
<x-dropdown>
|
||||
<x-slot:title>
|
||||
Actions
|
||||
</x-slot>
|
||||
@if (!str($application->status)->startsWith('exited'))
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='deploy'>
|
||||
Redeploy
|
||||
</div>
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='deploy'>
|
||||
Update Service
|
||||
</div>
|
||||
@else
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='restart'>
|
||||
Restart
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
|
||||
submitAction="stop" :checkboxes="$checkboxes" :actions="[
|
||||
'This application will be stopped.',
|
||||
'All non-persistent data of this application will be deleted.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:trigger>
|
||||
<div class="dropdown-item dropdown-item-touch text-error">
|
||||
Stop
|
||||
</div>
|
||||
</x-slot:trigger>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='deploy'>
|
||||
Deploy
|
||||
</div>
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<x-forms.button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2">
|
||||
<path
|
||||
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Update Service
|
||||
</x-forms.button>
|
||||
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<div class="mx-2 my-1 border-t border-neutral-200 dark:border-coolgray-300"></div>
|
||||
|
||||
@if ($application->status === 'running')
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='force_deploy_without_cache'>
|
||||
Force deploy (without cache)
|
||||
</div>
|
||||
@else
|
||||
<x-forms.button title="Restart without rebuilding" wire:click='restart'>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2">
|
||||
<path
|
||||
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Restart
|
||||
</x-forms.button>
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='deploy(true)'>
|
||||
Force deploy (without cache)
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
|
||||
submitAction="stop" :checkboxes="$checkboxes" :actions="[
|
||||
'This application will be stopped.',
|
||||
'All non-persistent data of this application will be deleted.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<x-forms.button wire:click='deploy'>
|
||||
<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
|
||||
</x-forms.button>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
|
||||
<div class="hidden flex-wrap items-center gap-2 md:flex">
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<div>
|
||||
<x-applications.advanced :application="$application" />
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@if (!str($application->status)->startsWith('exited'))
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<x-forms.button title="With rolling update if possible" wire:click='deploy'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-orange-400"
|
||||
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
|
||||
</path>
|
||||
<path d="M7.05 11.038v-3.988"></path>
|
||||
</svg>
|
||||
Redeploy
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<x-forms.button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2">
|
||||
<path
|
||||
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Update Service
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button title="Restart without rebuilding" wire:click='restart'>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2">
|
||||
<path
|
||||
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Restart
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@endif
|
||||
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
|
||||
submitAction="stop" :checkboxes="$checkboxes" :actions="[
|
||||
'This application will be stopped.',
|
||||
'All non-persistent data of this application will be deleted.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<x-forms.button wire:click='deploy'>
|
||||
<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
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -68,6 +68,20 @@
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if ($application->build_pack === 'dockerimage')
|
||||
<div class="flex flex-col gap-2 pt-4">
|
||||
<h3>Manual Preview Deployment</h3>
|
||||
<form wire:submit.prevent="addDockerImagePreview" class="flex flex-col gap-2 xl:flex-row xl:items-end">
|
||||
<x-forms.input id="manualPullRequestId" label="Pull Request Id"
|
||||
helper="Used as the preview identifier for naming, domains, logs, and cleanup." />
|
||||
<x-forms.input id="manualDockerTag" label="Docker Tag"
|
||||
helper="The image tag to deploy for this preview, for example pr_1234." />
|
||||
@can('deploy', $application)
|
||||
<x-forms.button type="submit">Deploy Preview</x-forms.button>
|
||||
@endcan
|
||||
</form>
|
||||
</div>
|
||||
@endif
|
||||
@if ($application->previews->count() > 0)
|
||||
<h3 class="py-4">Deployments</h3>
|
||||
<div class="flex flex-wrap w-full gap-4">
|
||||
@@ -87,11 +101,13 @@
|
||||
<x-external-link />
|
||||
</a>
|
||||
@endif
|
||||
|
|
||||
<a target="_blank" href="{{ data_get($preview, 'pull_request_html_url') }}">Open
|
||||
PR on Git
|
||||
<x-external-link />
|
||||
</a>
|
||||
@if (filled(data_get($preview, 'pull_request_html_url')))
|
||||
|
|
||||
<a target="_blank" href="{{ data_get($preview, 'pull_request_html_url') }}">Open
|
||||
PR on Git
|
||||
<x-external-link />
|
||||
</a>
|
||||
@endif
|
||||
@if (count($parameters) > 0)
|
||||
|
|
||||
<a {{ wireNavigate() }}
|
||||
@@ -131,6 +147,10 @@
|
||||
<form wire:submit="save_preview('{{ $preview->id }}')" class="flex items-end gap-2 pt-4">
|
||||
<x-forms.input label="Domain" helper="One domain per preview."
|
||||
id="previewFqdns.{{ $previewName }}" canGate="update" :canResource="$application"></x-forms.input>
|
||||
@if ($application->build_pack === 'dockerimage')
|
||||
<x-forms.input label="Docker Tag" helper="The image tag used for this preview deployment."
|
||||
id="previewDockerTags.{{ $previewName }}" canGate="update" :canResource="$application"></x-forms.input>
|
||||
@endif
|
||||
@can('update', $application)
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
<x-forms.button wire:click="generate_preview('{{ $preview->id }}')">Generate
|
||||
@@ -157,7 +177,8 @@
|
||||
Force deploy (without
|
||||
cache)
|
||||
</x-forms.button>
|
||||
<x-forms.button wire:click="deploy({{ data_get($preview, 'pull_request_id') }})">
|
||||
<x-forms.button
|
||||
wire:click="deploy({{ data_get($preview, 'pull_request_id') }}, null, false, '{{ data_get($preview, 'docker_registry_image_tag') }}')">
|
||||
@if (data_get($preview, 'status') === 'exited')
|
||||
<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"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<span>
|
||||
@if ($application->server_status === false)
|
||||
<span title="One or more servers are unreachable or misconfigured.">
|
||||
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
|
||||
</svg>
|
||||
</span>
|
||||
@elseif ($application->additional_servers()->exists() && str($application->status)->contains('degraded'))
|
||||
<span title="Application is in degraded state across multiple servers.">
|
||||
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
|
||||
</svg>
|
||||
</span>
|
||||
@endif
|
||||
</span>
|
||||
@@ -2,6 +2,7 @@
|
||||
<form wire:submit='submit' class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Swarm Configuration</h2>
|
||||
<x-deprecated-badge />
|
||||
@can('update', $application)
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
@@ -13,6 +14,9 @@
|
||||
</x-forms.button>
|
||||
@endcan
|
||||
</div>
|
||||
<x-callout type="warning" title="Deprecated" class="my-4">
|
||||
{{ config('deprecations.swarm') }}
|
||||
</x-callout>
|
||||
<div class="flex flex-col gap-2 py-4">
|
||||
<div class="flex flex-col items-end gap-2 xl:flex-row">
|
||||
<x-forms.input id="swarmReplicas" label="Replicas" required canGate="update" :canResource="$application" />
|
||||
|
||||
@@ -81,10 +81,10 @@
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Frequency" id="frequency" />
|
||||
<x-forms.input label="Frequency" id="frequency" required />
|
||||
<x-forms.input label="Timezone" id="timezone" disabled
|
||||
helper="The timezone of the server where the backup is scheduled to run (if not set, the instance timezone will be used)" />
|
||||
<x-forms.input label="Timeout" id="timeout" helper="The timeout of the backup job in seconds." />
|
||||
helper="The timezone of the server where the backup is scheduled to run (if not set, the instance timezone will be used)" required />
|
||||
<x-forms.input label="Timeout" id="timeout" type="number" min="60" helper="The timeout of the backup job in seconds." required />
|
||||
</div>
|
||||
|
||||
<h3 class="mt-6 mb-2 text-lg font-medium">Backup Retention Settings</h3>
|
||||
@@ -101,13 +101,13 @@
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Number of backups to keep" id="databaseBackupRetentionAmountLocally"
|
||||
type="number" min="0"
|
||||
helper="Keeps only the specified number of most recent backups on the server. Set to 0 for unlimited backups." />
|
||||
helper="Keeps only the specified number of most recent backups on the server. Set to 0 for unlimited backups." required />
|
||||
<x-forms.input label="Days to keep backups" id="databaseBackupRetentionDaysLocally" type="number"
|
||||
min="0"
|
||||
helper="Automatically removes backups older than the specified number of days. Set to 0 for no time limit." />
|
||||
helper="Automatically removes backups older than the specified number of days. Set to 0 for no time limit." required />
|
||||
<x-forms.input label="Maximum storage (GB)" id="databaseBackupRetentionMaxStorageLocally"
|
||||
type="number" min="0"
|
||||
helper="When total size of all backups in the current backup job exceeds this limit in GB, the oldest backups will be removed. Decimal values are supported (e.g. 0.001 for 1MB). Set to 0 for unlimited storage." />
|
||||
type="number" min="0" step="any"
|
||||
helper="When total size of all backups in the current backup job exceeds this limit in GB, the oldest backups will be removed. Decimal values are supported (e.g. 0.001 for 1MB). Set to 0 for unlimited storage." required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -117,13 +117,13 @@
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Number of backups to keep" id="databaseBackupRetentionAmountS3"
|
||||
type="number" min="0"
|
||||
helper="Keeps only the specified number of most recent backups on S3 storage. Set to 0 for unlimited backups." />
|
||||
helper="Keeps only the specified number of most recent backups on S3 storage. Set to 0 for unlimited backups." required />
|
||||
<x-forms.input label="Days to keep backups" id="databaseBackupRetentionDaysS3" type="number"
|
||||
min="0"
|
||||
helper="Automatically removes S3 backups older than the specified number of days. Set to 0 for no time limit." />
|
||||
helper="Automatically removes S3 backups older than the specified number of days. Set to 0 for no time limit." required />
|
||||
<x-forms.input label="Maximum storage (GB)" id="databaseBackupRetentionMaxStorageS3"
|
||||
type="number" min="0"
|
||||
helper="When total size of all backups in the current backup job exceeds this limit in GB, the oldest backups will be removed. Decimal values are supported (e.g. 0.5 for 500MB). Set to 0 for unlimited storage." />
|
||||
type="number" min="0" step="any"
|
||||
helper="When total size of all backups in the current backup job exceeds this limit in GB, the oldest backups will be removed. Decimal values are supported (e.g. 0.5 for 500MB). Set to 0 for unlimited storage." required />
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<x-forms.button type="submit" canGate="update" :canResource="$database">
|
||||
Save
|
||||
</x-forms.button>
|
||||
<x-modal-input title="Resource Details" buttonTitle="Details">
|
||||
<livewire:project.shared.resource-details :resource="$database" />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
|
||||
@@ -41,20 +44,8 @@
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input label="Clickhouse URL (internal)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="dbUrl" canGate="update" :canResource="$database" />
|
||||
@if ($dbUrlPublic)
|
||||
<x-forms.input label="Clickhouse URL (public)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="dbUrlPublic" canGate="update" :canResource="$database" />
|
||||
@else
|
||||
<x-forms.input label="Clickhouse URL (public)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
readonly value="Starting the database will generate this." canGate="update" :canResource="$database" />
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<livewire:project.database.clickhouse.status-info :database="$database" />
|
||||
<div class="flex flex-col py-2 w-64">
|
||||
<div class="flex items-center gap-2 pb-2">
|
||||
<div class="flex items-center">
|
||||
@@ -76,11 +67,12 @@
|
||||
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" canGate="update"
|
||||
:canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port"
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input type="number" placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port"
|
||||
canGate="update" :canResource="$database" />
|
||||
<x-forms.input placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
<x-forms.input type="number" placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<h3 class="pt-4">Advanced</h3>
|
||||
<div class="w-64">
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
@endcan
|
||||
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.database.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Webhooks</span></a>
|
||||
<a class='sub-menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.database.healthcheck', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Healthcheck</span></a>
|
||||
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
href="{{ route('project.database.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}"><span class="menu-item-label">Resource Limits</span></a>
|
||||
<a class="sub-menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||
@@ -57,6 +59,8 @@
|
||||
<livewire:project.shared.destination :resource="$database" />
|
||||
@elseif ($currentRoute === 'project.database.persistent-storage')
|
||||
<livewire:project.service.storage :resource="$database" />
|
||||
@elseif ($currentRoute === 'project.database.healthcheck')
|
||||
<livewire:project.database.health :database="$database" />
|
||||
@elseif ($currentRoute === 'project.database.import-backup')
|
||||
<livewire:project.database.import :resource="$database" />
|
||||
@elseif ($currentRoute === 'project.database.webhooks')
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<x-forms.button type="submit" canGate="update" :canResource="$database">
|
||||
Save
|
||||
</x-forms.button>
|
||||
<x-modal-input title="Resource Details" buttonTitle="Details">
|
||||
<livewire:project.shared.resource-details :resource="$database" />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
|
||||
@@ -37,60 +40,8 @@
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input label="Dragonfly URL (internal)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="dbUrl" canGate="update" :canResource="$database" />
|
||||
|
||||
@if ($dbUrlPublic)
|
||||
<x-forms.input label="Dragonfly URL (public)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="dbUrlPublic" canGate="update" :canResource="$database" />
|
||||
@else
|
||||
<x-forms.input label="Dragonfly URL (public)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
readonly value="Starting the database will generate this." canGate="update" :canResource="$database" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<h3>SSL Configuration</h3>
|
||||
@if ($database->enable_ssl && $certificateValidUntil)
|
||||
<x-modal-confirmation title="Regenerate SSL Certificates"
|
||||
buttonTitle="Regenerate SSL Certificates" :actions="[
|
||||
'The SSL certificate of this database will be regenerated.',
|
||||
'You must restart the database after regenerating the certificate to start using the new certificate.',
|
||||
]"
|
||||
submitAction="regenerateSslCertificate" :confirmWithText="false" :confirmWithPassword="false" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if ($database->enable_ssl && $certificateValidUntil)
|
||||
<span class="text-sm">Valid until:
|
||||
@if (now()->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
|
||||
@elseif(now()->addDays(30)->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expiring
|
||||
soon</span>
|
||||
@else
|
||||
<span>{{ $certificateValidUntil->format('d.m.Y H:i:s') }}</span>
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="w-64">
|
||||
@if (str($database->status)->contains('exited'))
|
||||
<x-forms.checkbox id="enable_ssl" label="Enable SSL" wire:model.live="enable_ssl"
|
||||
instantSave="instantSaveSSL" canGate="update" :canResource="$database" />
|
||||
@else
|
||||
<x-forms.checkbox id="enable_ssl" label="Enable SSL" wire:model.live="enable_ssl"
|
||||
instantSave="instantSaveSSL" disabled
|
||||
helper="Database should be stopped to change this settings." canGate="update"
|
||||
:canResource="$database" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<livewire:project.database.dragonfly.status-info :database="$database" />
|
||||
<div>
|
||||
<div class="flex flex-col py-2 w-64">
|
||||
<div class="flex items-center gap-2 pb-2">
|
||||
@@ -113,14 +64,15 @@
|
||||
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" canGate="update"
|
||||
:canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port"
|
||||
canGate="update" :canResource="$database" />
|
||||
<x-forms.input placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
</form>
|
||||
<h3 class="pt-4">Advanced</h3>
|
||||
<div class="w-64">
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input type="number" placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port"
|
||||
canGate="update" :canResource="$database" />
|
||||
<x-forms.input type="number" placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
</form>
|
||||
<h3 class="pt-4">Advanced</h3>
|
||||
<div class="w-64">
|
||||
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
|
||||
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs" canGate="update"
|
||||
:canResource="$database" />
|
||||
|
||||
@@ -10,18 +10,18 @@
|
||||
</x-slide-over>
|
||||
<div class="navbar-main">
|
||||
<nav
|
||||
class="flex overflow-x-scroll 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' : '' }}" {{ wireNavigate() }}
|
||||
class="scrollbar flex min-h-10 w-full flex-nowrap items-center gap-6 overflow-x-scroll overflow-y-hidden pb-1 whitespace-nowrap md:w-auto md:overflow-visible">
|
||||
<a class="shrink-0 {{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
href="{{ route('project.database.configuration', $parameters) }}">
|
||||
Configuration
|
||||
</a>
|
||||
|
||||
<a class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
|
||||
<a class="shrink-0 {{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('project.database.logs', $parameters) }}">
|
||||
Logs
|
||||
</a>
|
||||
@can('canAccessTerminal')
|
||||
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
|
||||
<a class="shrink-0 {{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('project.database.command', $parameters) }}">
|
||||
Terminal
|
||||
</a>
|
||||
@@ -31,7 +31,7 @@
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||
<a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
<a class="shrink-0 {{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
href="{{ route('project.database.backup.index', $parameters) }}">
|
||||
Backups
|
||||
</a>
|
||||
@@ -39,57 +39,97 @@
|
||||
</nav>
|
||||
@if ($database->destination->server->isFunctional())
|
||||
<div class="flex flex-wrap gap-2 items-center">
|
||||
@if (!str($database->status)->startsWith('exited'))
|
||||
<x-modal-confirmation title="Confirm Database Restart?" buttonTitle="Restart" submitAction="restart"
|
||||
:actions="[
|
||||
'This database will be unavailable during the restart.',
|
||||
'If the database is currently in use data could be lost.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Database"
|
||||
:dispatchEvent="true" dispatchEventType="restartEvent">
|
||||
<x-slot:button-title>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Restart
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation title="Confirm Database Stopping?" buttonTitle="Stop" submitAction="stop"
|
||||
:checkboxes="$checkboxes" :actions="[
|
||||
'This database will be stopped.',
|
||||
'If the database is currently in use data could be lost.',
|
||||
'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
<div class="md:hidden">
|
||||
<x-dropdown>
|
||||
<x-slot:title>
|
||||
Actions
|
||||
</x-slot>
|
||||
@if (!str($database->status)->startsWith('exited'))
|
||||
<x-modal-confirmation title="Confirm Database Restart?" buttonTitle="Restart" submitAction="restart"
|
||||
:actions="[
|
||||
'This database will be unavailable during the restart.',
|
||||
'If the database is currently in use data could be lost.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Database"
|
||||
:dispatchEvent="true" dispatchEventType="restartEvent">
|
||||
<x-slot:trigger>
|
||||
<div class="dropdown-item dropdown-item-touch">
|
||||
Restart
|
||||
</div>
|
||||
</x-slot:trigger>
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation title="Confirm Database Stopping?" buttonTitle="Stop" submitAction="stop"
|
||||
:checkboxes="$checkboxes" :actions="[
|
||||
'This database will be stopped.',
|
||||
'If the database is currently in use data could be lost.',
|
||||
'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:trigger>
|
||||
<div class="dropdown-item dropdown-item-touch text-error">
|
||||
Stop
|
||||
</div>
|
||||
</x-slot:trigger>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('startEvent')">
|
||||
Start
|
||||
</div>
|
||||
@endif
|
||||
</x-dropdown>
|
||||
</div>
|
||||
<div class="hidden flex-wrap items-center gap-2 md:flex">
|
||||
@if (!str($database->status)->startsWith('exited'))
|
||||
<x-modal-confirmation title="Confirm Database Restart?" buttonTitle="Restart" submitAction="restart"
|
||||
:actions="[
|
||||
'This database will be unavailable during the restart.',
|
||||
'If the database is currently in use data could be lost.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Database"
|
||||
:dispatchEvent="true" dispatchEventType="restartEvent">
|
||||
<x-slot:button-title>
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Restart
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
<x-modal-confirmation title="Confirm Database Stopping?" buttonTitle="Stop" submitAction="stop"
|
||||
:checkboxes="$checkboxes" :actions="[
|
||||
'This database will be stopped.',
|
||||
'If the database is currently in use data could be lost.',
|
||||
'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<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>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<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>
|
||||
Start
|
||||
</button>
|
||||
@endif
|
||||
Start
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
@script
|
||||
<script>
|
||||
$wire.$on('startEvent', () => {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<form wire:submit='submit' class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Healthcheck</h2>
|
||||
<x-forms.button canGate="update" :canResource="$database" type="submit">Save</x-forms.button>
|
||||
@if (!$healthCheckEnabled)
|
||||
<x-modal-confirmation title="Confirm Healthcheck Enable?" buttonTitle="Enable Healthcheck"
|
||||
submitAction="toggleHealthcheck" :actions="['Enable healthcheck for this database.']"
|
||||
warningMessage="If the health check fails, this database will be marked unhealthy. Please review the <a href='https://coolify.io/docs/knowledge-base/health-checks' target='_blank' class='underline text-white'>Health Checks</a> guide before proceeding!"
|
||||
step2ButtonText="Enable Healthcheck" :confirmWithText="false" :confirmWithPassword="false"
|
||||
isHighlightedButton>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<x-forms.button canGate="update" :canResource="$database" wire:click="toggleHealthcheck">Disable Healthcheck</x-forms.button>
|
||||
@endif
|
||||
</div>
|
||||
<div class="mt-1 pb-4">Define how your resource's health should be checked.</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
@if (!$healthCheckEnabled)
|
||||
<x-callout type="warning" title="Healthcheck disabled">
|
||||
<p>Docker runs no healthcheck probe for this database and Coolify can no longer report a healthy/unhealthy state.</p>
|
||||
</x-callout>
|
||||
@endif
|
||||
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$database" min="1" type="number" id="healthCheckInterval"
|
||||
placeholder="15" label="Interval (s)" required />
|
||||
<x-forms.input canGate="update" :canResource="$database" min="1" type="number" id="healthCheckTimeout"
|
||||
placeholder="5" label="Timeout (s)" required />
|
||||
<x-forms.input canGate="update" :canResource="$database" min="1" type="number" id="healthCheckRetries"
|
||||
placeholder="5" label="Retries" required />
|
||||
<x-forms.input canGate="update" :canResource="$database" min="0" type="number"
|
||||
id="healthCheckStartPeriod" placeholder="5" label="Start Period (s)" required />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,228 @@
|
||||
<div x-data="{
|
||||
error: $wire.entangle('error'),
|
||||
filesize: $wire.entangle('filesize'),
|
||||
filename: $wire.entangle('filename'),
|
||||
isUploading: $wire.entangle('isUploading'),
|
||||
progress: $wire.entangle('progress'),
|
||||
s3FileSize: $wire.entangle('s3FileSize'),
|
||||
s3StorageId: $wire.entangle('s3StorageId'),
|
||||
s3Path: $wire.entangle('s3Path'),
|
||||
restoreType: null
|
||||
}">
|
||||
<script type="text/javascript" src="{{ URL::asset('js/dropzone.js') }}"></script>
|
||||
@script
|
||||
<script data-navigate-once>
|
||||
Dropzone.options.myDropzone = {
|
||||
chunking: true,
|
||||
method: "POST",
|
||||
maxFilesize: 1000000000,
|
||||
chunkSize: 10000000,
|
||||
createImageThumbnails: false,
|
||||
disablePreviews: true,
|
||||
parallelChunkUploads: false,
|
||||
init: function () {
|
||||
let button = this.element.querySelector('button');
|
||||
button.innerText = 'Select or drop a backup file here.'
|
||||
this.on('sending', function (file, xhr, formData) {
|
||||
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
formData.append("_token", token);
|
||||
});
|
||||
this.on("addedfile", file => {
|
||||
$wire.isUploading = true;
|
||||
$wire.customLocation = '';
|
||||
});
|
||||
this.on('uploadprogress', function (file, progress, bytesSent) {
|
||||
$wire.progress = progress;
|
||||
});
|
||||
this.on('complete', function (file) {
|
||||
$wire.filename = file.name;
|
||||
$wire.filesize = Number(file.size / 1024 / 1024).toFixed(2) + ' MB';
|
||||
$wire.isUploading = false;
|
||||
});
|
||||
this.on('error', function (file, message) {
|
||||
$wire.error = true;
|
||||
$wire.$dispatch('error', message.error)
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@endscript
|
||||
<div class="pt-2 rounded-sm alert-error">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<span>This is a destructive action, existing data will be replaced!</span>
|
||||
</div>
|
||||
{{-- Restore Command Configuration --}}
|
||||
@if ($resourceDbType === 'standalone-postgresql')
|
||||
@if ($dumpAll)
|
||||
<x-forms.textarea rows="6" readonly label="Custom Import Command"
|
||||
wire:model='restoreCommandText' canGate="update" :canResource="$this->resource"></x-forms.textarea>
|
||||
@else
|
||||
<x-forms.input label="Custom Import Command" wire:model='postgresqlRestoreCommand' canGate="update" :canResource="$this->resource"></x-forms.input>
|
||||
<div class="flex flex-col gap-1 pt-1">
|
||||
<span class="text-xs">You can add "--clean" to drop objects before creating them, avoiding
|
||||
conflicts.</span>
|
||||
<span class="text-xs">You can add "--verbose" to log more things.</span>
|
||||
</div>
|
||||
@endif
|
||||
<div class="w-64 pt-2">
|
||||
<x-forms.checkbox label="Backup includes all databases" wire:model.live='dumpAll' canGate="update" :canResource="$this->resource"></x-forms.checkbox>
|
||||
</div>
|
||||
@elseif ($resourceDbType === 'standalone-mysql')
|
||||
@if ($dumpAll)
|
||||
<x-forms.textarea rows="14" readonly label="Custom Import Command"
|
||||
wire:model='restoreCommandText' canGate="update" :canResource="$this->resource"></x-forms.textarea>
|
||||
@else
|
||||
<x-forms.input label="Custom Import Command" wire:model='mysqlRestoreCommand' canGate="update" :canResource="$this->resource"></x-forms.input>
|
||||
@endif
|
||||
<div class="w-64 pt-2">
|
||||
<x-forms.checkbox label="Backup includes all databases" wire:model.live='dumpAll' canGate="update" :canResource="$this->resource"></x-forms.checkbox>
|
||||
</div>
|
||||
@elseif ($resourceDbType === 'standalone-mariadb')
|
||||
@if ($dumpAll)
|
||||
<x-forms.textarea rows="14" readonly label="Custom Import Command"
|
||||
wire:model='restoreCommandText' canGate="update" :canResource="$this->resource"></x-forms.textarea>
|
||||
@else
|
||||
<x-forms.input label="Custom Import Command" wire:model='mariadbRestoreCommand' canGate="update" :canResource="$this->resource"></x-forms.input>
|
||||
@endif
|
||||
<div class="w-64 pt-2">
|
||||
<x-forms.checkbox label="Backup includes all databases" wire:model.live='dumpAll' canGate="update" :canResource="$this->resource"></x-forms.checkbox>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Restore Type Selection Boxes --}}
|
||||
<h3 class="pt-6">Choose Restore Method</h3>
|
||||
<div class="flex gap-4 pt-2">
|
||||
<div @click="restoreType = 'file'"
|
||||
class="flex-1 p-6 border-2 rounded-sm cursor-pointer transition-all"
|
||||
:class="restoreType === 'file' ? 'border-warning bg-warning/10' : 'border-neutral-200 dark:border-neutral-800 hover:border-warning/50'">
|
||||
<div class="flex flex-col gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||
</svg>
|
||||
<h4 class="text-lg font-bold">Restore from File</h4>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">Upload a backup file or specify a file path on the server</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (count($availableS3Storages) > 0)
|
||||
<div @click="restoreType = 's3'"
|
||||
class="flex-1 p-6 border-2 rounded-sm cursor-pointer transition-all"
|
||||
:class="restoreType === 's3' ? 'border-warning bg-warning/10' : 'border-neutral-200 dark:border-neutral-800 hover:border-warning/50'">
|
||||
<div class="flex flex-col gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z" />
|
||||
</svg>
|
||||
<h4 class="text-lg font-bold">Restore from S3</h4>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">Download and restore a backup from S3 storage</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- File Restore Section --}}
|
||||
@can('update', $this->resource)
|
||||
<div x-show="restoreType === 'file'" class="pt-6">
|
||||
<h3>Backup File</h3>
|
||||
<form class="flex gap-2 items-end pt-2">
|
||||
<x-forms.input label="Location of the backup file on the server" placeholder="e.g. /home/user/backup.sql.gz"
|
||||
wire:model='customLocation' x-model="$wire.customLocation" canGate="update" :canResource="$this->resource"></x-forms.input>
|
||||
<x-forms.button class="w-full" wire:click='checkFile' x-bind:disabled="!$wire.customLocation" canGate="update" :canResource="$this->resource">Check File</x-forms.button>
|
||||
</form>
|
||||
<div class="pt-2 text-center text-xl font-bold">
|
||||
Or
|
||||
</div>
|
||||
<form action="{{ route('upload.backup', ['databaseUuid' => $resourceUuid]) }}" class="dropzone" id="my-dropzone" wire:ignore>
|
||||
@csrf
|
||||
</form>
|
||||
<div x-show="isUploading">
|
||||
<progress max="100" x-bind:value="progress" class="progress progress-warning"></progress>
|
||||
</div>
|
||||
|
||||
<div x-show="filename && !error" class="pt-6">
|
||||
<h3>File Information</h3>
|
||||
<div class="pt-2">Location: <span x-text="filename ?? 'N/A'"></span><span x-show="filesize" x-text="' / ' + filesize"></span></div>
|
||||
<div class="pt-2">
|
||||
<x-modal-confirmation title="Restore Database from File?" buttonTitle="Restore from File"
|
||||
submitAction="runImport" isErrorButton>
|
||||
<x-slot:button-title>
|
||||
Restore Database from File
|
||||
</x-slot:button-title>
|
||||
This will perform the following actions:
|
||||
<ul class="list-disc list-inside pt-2">
|
||||
<li>Copy backup file to database container</li>
|
||||
<li>Execute restore command</li>
|
||||
</ul>
|
||||
<div class="pt-2 font-bold text-error">WARNING: This will REPLACE all existing data!</div>
|
||||
</x-modal-confirmation>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
|
||||
{{-- S3 Restore Section --}}
|
||||
@if (count($availableS3Storages) > 0)
|
||||
@can('update', $this->resource)
|
||||
<div x-show="restoreType === 's3'" class="pt-6">
|
||||
<h3>Restore from S3</h3>
|
||||
<div class="flex flex-col gap-2 pt-2">
|
||||
<x-forms.select label="S3 Storage" wire:model.live="s3StorageId" canGate="update" :canResource="$this->resource">
|
||||
<option value="">Select S3 Storage</option>
|
||||
@foreach ($availableS3Storages as $storage)
|
||||
<option value="{{ $storage['id'] }}">{{ $storage['name'] }}
|
||||
@if ($storage['description'])
|
||||
- {{ $storage['description'] }}
|
||||
@endif
|
||||
</option>
|
||||
@endforeach
|
||||
</x-forms.select>
|
||||
|
||||
<x-forms.input label="S3 File Path (within bucket)"
|
||||
helper="Path to the backup file in your S3 bucket, e.g., /backups/database-2025-01-15.gz"
|
||||
placeholder="/backups/database-backup.gz" wire:model.blur='s3Path'
|
||||
wire:keydown.enter='checkS3File' canGate="update" :canResource="$this->resource"></x-forms.input>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<x-forms.button class="w-full" wire:click='checkS3File' x-bind:disabled="!s3StorageId || !s3Path" canGate="update" :canResource="$this->resource">
|
||||
Check File
|
||||
</x-forms.button>
|
||||
</div>
|
||||
|
||||
@if ($s3FileSize)
|
||||
<div class="pt-6">
|
||||
<h3>File Information</h3>
|
||||
<div class="pt-2">Location: {{ $s3Path }} {{ formatBytes($s3FileSize ?? 0) }}</div>
|
||||
<div class="pt-2">
|
||||
<x-modal-confirmation title="Restore Database from S3?" buttonTitle="Restore from S3"
|
||||
submitAction="restoreFromS3" isErrorButton>
|
||||
<x-slot:button-title>
|
||||
Restore Database from S3
|
||||
</x-slot:button-title>
|
||||
This will perform the following actions:
|
||||
<ul class="list-disc list-inside pt-2">
|
||||
<li>Download backup from S3 storage</li>
|
||||
<li>Copy file into database container</li>
|
||||
<li>Execute restore command</li>
|
||||
</ul>
|
||||
<div class="pt-2 font-bold text-error">WARNING: This will REPLACE all existing data!</div>
|
||||
</x-modal-confirmation>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
@endif
|
||||
|
||||
{{-- Slide-over for activity monitor (all restore operations) --}}
|
||||
<x-slide-over @databaserestore.window="slideOverOpen = true" closeWithX fullScreen>
|
||||
<x-slot:title>Database Restore Output</x-slot:title>
|
||||
<x-slot:content>
|
||||
<div wire:ignore>
|
||||
<livewire:activity-monitor wire:key="database-restore-{{ $resourceUuid }}" header="Logs" fullHeight />
|
||||
</div>
|
||||
</x-slot:content>
|
||||
</x-slide-over>
|
||||
</div>
|
||||
@@ -1,237 +1,10 @@
|
||||
<div x-data="{
|
||||
error: $wire.entangle('error'),
|
||||
filesize: $wire.entangle('filesize'),
|
||||
filename: $wire.entangle('filename'),
|
||||
isUploading: $wire.entangle('isUploading'),
|
||||
progress: $wire.entangle('progress'),
|
||||
s3FileSize: $wire.entangle('s3FileSize'),
|
||||
s3StorageId: $wire.entangle('s3StorageId'),
|
||||
s3Path: $wire.entangle('s3Path'),
|
||||
restoreType: null
|
||||
}">
|
||||
<script type="text/javascript" src="{{ URL::asset('js/dropzone.js') }}"></script>
|
||||
@script
|
||||
<script data-navigate-once>
|
||||
Dropzone.options.myDropzone = {
|
||||
chunking: true,
|
||||
method: "POST",
|
||||
maxFilesize: 1000000000,
|
||||
chunkSize: 10000000,
|
||||
createImageThumbnails: false,
|
||||
disablePreviews: true,
|
||||
parallelChunkUploads: false,
|
||||
init: function () {
|
||||
let button = this.element.querySelector('button');
|
||||
button.innerText = 'Select or drop a backup file here.'
|
||||
this.on('sending', function (file, xhr, formData) {
|
||||
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
formData.append("_token", token);
|
||||
});
|
||||
this.on("addedfile", file => {
|
||||
$wire.isUploading = true;
|
||||
$wire.customLocation = '';
|
||||
});
|
||||
this.on('uploadprogress', function (file, progress, bytesSent) {
|
||||
$wire.progress = progress;
|
||||
});
|
||||
this.on('complete', function (file) {
|
||||
$wire.filename = file.name;
|
||||
$wire.filesize = Number(file.size / 1024 / 1024).toFixed(2) + ' MB';
|
||||
$wire.isUploading = false;
|
||||
});
|
||||
this.on('error', function (file, message) {
|
||||
$wire.error = true;
|
||||
$wire.$dispatch('error', message.error)
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@endscript
|
||||
<div>
|
||||
<h2>Import Backup</h2>
|
||||
@if ($unsupported)
|
||||
<div>Database restore is not supported.</div>
|
||||
@elseif (str($resourceStatus)->startsWith('running'))
|
||||
<livewire:project.database.import-form wire:key="database-import-form-{{ $resourceUuid }}" />
|
||||
@else
|
||||
<div class="pt-2 rounded-sm alert-error">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<span>This is a destructive action, existing data will be replaced!</span>
|
||||
</div>
|
||||
@if (str($resourceStatus)->startsWith('running'))
|
||||
{{-- Restore Command Configuration --}}
|
||||
@if ($resourceDbType === 'standalone-postgresql')
|
||||
@if ($dumpAll)
|
||||
<x-forms.textarea rows="6" readonly label="Custom Import Command"
|
||||
wire:model='restoreCommandText'></x-forms.textarea>
|
||||
@else
|
||||
<x-forms.input label="Custom Import Command" wire:model='postgresqlRestoreCommand'></x-forms.input>
|
||||
<div class="flex flex-col gap-1 pt-1">
|
||||
<span class="text-xs">You can add "--clean" to drop objects before creating them, avoiding
|
||||
conflicts.</span>
|
||||
<span class="text-xs">You can add "--verbose" to log more things.</span>
|
||||
</div>
|
||||
@endif
|
||||
<div class="w-64 pt-2">
|
||||
<x-forms.checkbox label="Backup includes all databases" wire:model.live='dumpAll'></x-forms.checkbox>
|
||||
</div>
|
||||
@elseif ($resourceDbType === 'standalone-mysql')
|
||||
@if ($dumpAll)
|
||||
<x-forms.textarea rows="14" readonly label="Custom Import Command"
|
||||
wire:model='restoreCommandText'></x-forms.textarea>
|
||||
@else
|
||||
<x-forms.input label="Custom Import Command" wire:model='mysqlRestoreCommand'></x-forms.input>
|
||||
@endif
|
||||
<div class="w-64 pt-2">
|
||||
<x-forms.checkbox label="Backup includes all databases" wire:model.live='dumpAll'></x-forms.checkbox>
|
||||
</div>
|
||||
@elseif ($resourceDbType === 'standalone-mariadb')
|
||||
@if ($dumpAll)
|
||||
<x-forms.textarea rows="14" readonly label="Custom Import Command"
|
||||
wire:model='restoreCommandText'></x-forms.textarea>
|
||||
@else
|
||||
<x-forms.input label="Custom Import Command" wire:model='mariadbRestoreCommand'></x-forms.input>
|
||||
@endif
|
||||
<div class="w-64 pt-2">
|
||||
<x-forms.checkbox label="Backup includes all databases" wire:model.live='dumpAll'></x-forms.checkbox>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Restore Type Selection Boxes --}}
|
||||
<h3 class="pt-6">Choose Restore Method</h3>
|
||||
<div class="flex gap-4 pt-2">
|
||||
<div @click="restoreType = 'file'"
|
||||
class="flex-1 p-6 border-2 rounded-sm cursor-pointer transition-all"
|
||||
:class="restoreType === 'file' ? 'border-warning bg-warning/10' : 'border-neutral-200 dark:border-neutral-800 hover:border-warning/50'">
|
||||
<div class="flex flex-col gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||
</svg>
|
||||
<h4 class="text-lg font-bold">Restore from File</h4>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">Upload a backup file or specify a file path on the server</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (count($availableS3Storages) > 0)
|
||||
<div @click="restoreType = 's3'"
|
||||
class="flex-1 p-6 border-2 rounded-sm cursor-pointer transition-all"
|
||||
:class="restoreType === 's3' ? 'border-warning bg-warning/10' : 'border-neutral-200 dark:border-neutral-800 hover:border-warning/50'">
|
||||
<div class="flex flex-col gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z" />
|
||||
</svg>
|
||||
<h4 class="text-lg font-bold">Restore from S3</h4>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">Download and restore a backup from S3 storage</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- File Restore Section --}}
|
||||
@can('update', $this->resource)
|
||||
<div x-show="restoreType === 'file'" class="pt-6">
|
||||
<h3>Backup File</h3>
|
||||
<form class="flex gap-2 items-end pt-2">
|
||||
<x-forms.input label="Location of the backup file on the server" placeholder="e.g. /home/user/backup.sql.gz"
|
||||
wire:model='customLocation' x-model="$wire.customLocation"></x-forms.input>
|
||||
<x-forms.button class="w-full" wire:click='checkFile' x-bind:disabled="!$wire.customLocation">Check File</x-forms.button>
|
||||
</form>
|
||||
<div class="pt-2 text-center text-xl font-bold">
|
||||
Or
|
||||
</div>
|
||||
<form action="/upload/backup/{{ $resourceUuid }}" class="dropzone" id="my-dropzone" wire:ignore>
|
||||
@csrf
|
||||
</form>
|
||||
<div x-show="isUploading">
|
||||
<progress max="100" x-bind:value="progress" class="progress progress-warning"></progress>
|
||||
</div>
|
||||
|
||||
<div x-show="filename && !error" class="pt-6">
|
||||
<h3>File Information</h3>
|
||||
<div class="pt-2">Location: <span x-text="filename ?? 'N/A'"></span><span x-show="filesize" x-text="' / ' + filesize"></span></div>
|
||||
<div class="pt-2">
|
||||
<x-modal-confirmation title="Restore Database from File?" buttonTitle="Restore from File"
|
||||
submitAction="runImport" isErrorButton>
|
||||
<x-slot:button-title>
|
||||
Restore Database from File
|
||||
</x-slot:button-title>
|
||||
This will perform the following actions:
|
||||
<ul class="list-disc list-inside pt-2">
|
||||
<li>Copy backup file to database container</li>
|
||||
<li>Execute restore command</li>
|
||||
</ul>
|
||||
<div class="pt-2 font-bold text-error">WARNING: This will REPLACE all existing data!</div>
|
||||
</x-modal-confirmation>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
|
||||
{{-- S3 Restore Section --}}
|
||||
@if (count($availableS3Storages) > 0)
|
||||
@can('update', $this->resource)
|
||||
<div x-show="restoreType === 's3'" class="pt-6">
|
||||
<h3>Restore from S3</h3>
|
||||
<div class="flex flex-col gap-2 pt-2">
|
||||
<x-forms.select label="S3 Storage" wire:model.live="s3StorageId">
|
||||
<option value="">Select S3 Storage</option>
|
||||
@foreach ($availableS3Storages as $storage)
|
||||
<option value="{{ $storage['id'] }}">{{ $storage['name'] }}
|
||||
@if ($storage['description'])
|
||||
- {{ $storage['description'] }}
|
||||
@endif
|
||||
</option>
|
||||
@endforeach
|
||||
</x-forms.select>
|
||||
|
||||
<x-forms.input label="S3 File Path (within bucket)"
|
||||
helper="Path to the backup file in your S3 bucket, e.g., /backups/database-2025-01-15.gz"
|
||||
placeholder="/backups/database-backup.gz" wire:model.blur='s3Path'
|
||||
wire:keydown.enter='checkS3File'></x-forms.input>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<x-forms.button class="w-full" wire:click='checkS3File' x-bind:disabled="!s3StorageId || !s3Path">
|
||||
Check File
|
||||
</x-forms.button>
|
||||
</div>
|
||||
|
||||
@if ($s3FileSize)
|
||||
<div class="pt-6">
|
||||
<h3>File Information</h3>
|
||||
<div class="pt-2">Location: {{ $s3Path }} {{ formatBytes($s3FileSize ?? 0) }}</div>
|
||||
<div class="pt-2">
|
||||
<x-modal-confirmation title="Restore Database from S3?" buttonTitle="Restore from S3"
|
||||
submitAction="restoreFromS3" isErrorButton>
|
||||
<x-slot:button-title>
|
||||
Restore Database from S3
|
||||
</x-slot:button-title>
|
||||
This will perform the following actions:
|
||||
<ul class="list-disc list-inside pt-2">
|
||||
<li>Download backup from S3 storage</li>
|
||||
<li>Copy file into database container</li>
|
||||
<li>Execute restore command</li>
|
||||
</ul>
|
||||
<div class="pt-2 font-bold text-error">WARNING: This will REPLACE all existing data!</div>
|
||||
</x-modal-confirmation>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
@endif
|
||||
|
||||
{{-- Slide-over for activity monitor (all restore operations) --}}
|
||||
<x-slide-over @databaserestore.window="slideOverOpen = true" closeWithX fullScreen>
|
||||
<x-slot:title>Database Restore Output</x-slot:title>
|
||||
<x-slot:content>
|
||||
<div wire:ignore>
|
||||
<livewire:activity-monitor wire:key="database-restore-{{ $resourceUuid }}" header="Logs" fullHeight />
|
||||
</div>
|
||||
</x-slot:content>
|
||||
</x-slide-over>
|
||||
@else
|
||||
<div>Database must be running to restore a backup.</div>
|
||||
@endif
|
||||
<div>Database must be running to restore a backup.</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -5,6 +5,9 @@
|
||||
<x-forms.button type="submit" canGate="update" :canResource="$database">
|
||||
Save
|
||||
</x-forms.button>
|
||||
<x-modal-input title="Resource Details" buttonTitle="Details">
|
||||
<livewire:project.shared.resource-details :resource="$database" />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
|
||||
@@ -38,59 +41,8 @@
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input label="KeyDB URL (internal)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="dbUrl" canGate="update" :canResource="$database" />
|
||||
@if ($dbUrlPublic)
|
||||
<x-forms.input label="KeyDB URL (public)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="dbUrlPublic" canGate="update" :canResource="$database" />
|
||||
@else
|
||||
<x-forms.input label="KeyDB URL (public)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
readonly value="Starting the database will generate this." canGate="update" :canResource="$database" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<h3>SSL Configuration</h3>
|
||||
@if ($database->enable_ssl && $certificateValidUntil)
|
||||
<x-modal-confirmation title="Regenerate SSL Certificates"
|
||||
buttonTitle="Regenerate SSL Certificates" :actions="[
|
||||
'The SSL certificate of this database will be regenerated.',
|
||||
'You must restart the database after regenerating the certificate to start using the new certificate.',
|
||||
]"
|
||||
submitAction="regenerateSslCertificate" :confirmWithText="false" :confirmWithPassword="false" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if ($database->enable_ssl && $certificateValidUntil)
|
||||
<span class="text-sm">Valid until:
|
||||
@if (now()->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
|
||||
@elseif(now()->addDays(30)->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expiring
|
||||
soon</span>
|
||||
@else
|
||||
<span>{{ $certificateValidUntil->format('d.m.Y H:i:s') }}</span>
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="w-64">
|
||||
@if (str($database->status)->contains('exited'))
|
||||
<x-forms.checkbox id="enable_ssl" label="Enable SSL" wire:model.live="enable_ssl"
|
||||
instantSave="instantSaveSSL" canGate="update" :canResource="$database" />
|
||||
@else
|
||||
<x-forms.checkbox id="enable_ssl" label="Enable SSL" wire:model.live="enable_ssl"
|
||||
instantSave="instantSaveSSL" disabled
|
||||
helper="Database should be stopped to change this settings." canGate="update"
|
||||
:canResource="$database" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<livewire:project.database.keydb.status-info :database="$database" />
|
||||
<div>
|
||||
<div class="flex flex-col py-2 w-64">
|
||||
<div class="flex items-center gap-2 pb-2">
|
||||
@@ -113,14 +65,15 @@
|
||||
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" canGate="update"
|
||||
:canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port"
|
||||
canGate="update" :canResource="$database" />
|
||||
<x-forms.input placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input type="number" placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port"
|
||||
canGate="update" :canResource="$database" />
|
||||
<x-forms.input type="number" placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
<x-forms.textarea
|
||||
helper="<a target='_blank' class='underline dark:text-white' href='https://raw.githubusercontent.com/Snapchat/KeyDB/unstable/keydb.conf'>KeyDB Default Configuration</a>"
|
||||
label="Custom KeyDB Configuration" rows="10" id="keydbConf" canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
</form>
|
||||
<h3 class="pt-4">Advanced</h3>
|
||||
<div class="w-64">
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<x-forms.button type="submit" canGate="update" :canResource="$database">
|
||||
Save
|
||||
</x-forms.button>
|
||||
<x-modal-input title="Resource Details" buttonTitle="Details">
|
||||
<livewire:project.shared.resource-details :resource="$database" />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
|
||||
@@ -61,59 +64,9 @@
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input label="MariaDB URL (internal)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="db_url" canGate="update" :canResource="$database" />
|
||||
@if ($db_url_public)
|
||||
<x-forms.input label="MariaDB URL (public)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="db_url_public" canGate="update" :canResource="$database" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<h3>SSL Configuration</h3>
|
||||
@if ($enableSsl && $certificateValidUntil)
|
||||
<x-modal-confirmation title="Regenerate SSL Certificates"
|
||||
buttonTitle="Regenerate SSL Certificates" :actions="[
|
||||
'The SSL certificate of this database will be regenerated.',
|
||||
'You must restart the database after regenerating the certificate to start using the new certificate.',
|
||||
]"
|
||||
submitAction="regenerateSslCertificate" :confirmWithText="false" :confirmWithPassword="false" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if ($enableSsl && $certificateValidUntil)
|
||||
<span class="text-sm">Valid until:
|
||||
@if (now()->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
|
||||
@elseif(now()->addDays(30)->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expiring
|
||||
soon</span>
|
||||
@else
|
||||
<span>{{ $certificateValidUntil->format('d.m.Y H:i:s') }}</span>
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="w-64">
|
||||
@if (str($database->status)->contains('exited'))
|
||||
<x-forms.checkbox id="enableSsl" label="Enable SSL"
|
||||
wire:model.live="enableSsl" instantSave="instantSaveSSL" canGate="update"
|
||||
:canResource="$database" />
|
||||
@else
|
||||
<x-forms.checkbox id="enableSsl" label="Enable SSL"
|
||||
wire:model.live="enableSsl" instantSave="instantSaveSSL" disabled
|
||||
helper="Database should be stopped to change this settings." canGate="update"
|
||||
:canResource="$database" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<livewire:project.database.mariadb.status-info :database="$database" />
|
||||
|
||||
<div>
|
||||
<div class="flex flex-col py-2 w-64">
|
||||
@@ -137,10 +90,12 @@
|
||||
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}"
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input type="number" placeholder="5432" disabled="{{ $isPublic }}"
|
||||
id="publicPort" label="Public Port" canGate="update" :canResource="$database" />
|
||||
<x-forms.input placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
<x-forms.input type="number" placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
</div>
|
||||
<x-forms.textarea label="Custom MariaDB Configuration" rows="10" id="mariadbConf"
|
||||
canGate="update" :canResource="$database" />
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<x-forms.button type="submit" canGate="update" :canResource="$database">
|
||||
Save
|
||||
</x-forms.button>
|
||||
<x-modal-input title="Resource Details" buttonTitle="Details">
|
||||
<livewire:project.shared.resource-details :resource="$database" />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
|
||||
@@ -50,85 +53,10 @@
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input label="Mongo URL (internal)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="db_url" canGate="update" :canResource="$database" />
|
||||
@if ($db_url_public)
|
||||
<x-forms.input label="Mongo URL (public)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="db_url_public" canGate="update" :canResource="$database" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<livewire:project.database.mongodb.status-info :database="$database" />
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<h3>SSL Configuration</h3>
|
||||
@if ($enableSsl)
|
||||
<x-modal-confirmation title="Regenerate SSL Certificates"
|
||||
buttonTitle="Regenerate SSL Certificates" :actions="[
|
||||
'The SSL certificate of this database will be regenerated.',
|
||||
'You must restart the database after regenerating the certificate to start using the new certificate.',
|
||||
]"
|
||||
submitAction="regenerateSslCertificate" :confirmWithText="false" :confirmWithPassword="false" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if ($enableSsl && $certificateValidUntil)
|
||||
<span class="text-sm">Valid until:
|
||||
@if (now()->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
|
||||
@elseif(now()->addDays(30)->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expiring
|
||||
soon</span>
|
||||
@else
|
||||
<span>{{ $certificateValidUntil->format('d.m.Y H:i:s') }}</span>
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="w-64">
|
||||
@if (str($database->status)->contains('exited'))
|
||||
<x-forms.checkbox id="enableSsl" label="Enable SSL"
|
||||
wire:model.live="enableSsl" instantSave="instantSaveSSL" canGate="update"
|
||||
:canResource="$database" />
|
||||
@else
|
||||
<x-forms.checkbox id="enableSsl" label="Enable SSL"
|
||||
wire:model.live="enableSsl" instantSave="instantSaveSSL" disabled
|
||||
helper="Database should be stopped to change this settings." canGate="update"
|
||||
:canResource="$database" />
|
||||
@endif
|
||||
</div>
|
||||
@if ($enableSsl)
|
||||
<div class="mx-2">
|
||||
@if (str($database->status)->contains('exited'))
|
||||
<x-forms.select id="sslMode" label="SSL Mode" wire:model.live="sslMode"
|
||||
instantSave="instantSaveSSL"
|
||||
helper="Choose the SSL verification mode for MongoDB connections" canGate="update"
|
||||
:canResource="$database">
|
||||
<option value="allow" title="Allow insecure connections">allow (insecure)</option>
|
||||
<option value="prefer" title="Prefer secure connections">prefer (secure)</option>
|
||||
<option value="require" title="Require secure connections">require (secure)</option>
|
||||
<option value="verify-full" title="Verify full certificate">verify-full (secure)
|
||||
</option>
|
||||
</x-forms.select>
|
||||
@else
|
||||
<x-forms.select id="sslMode" label="SSL Mode" instantSave="instantSaveSSL"
|
||||
disabled helper="Database should be stopped to change this settings." canGate="update"
|
||||
:canResource="$database">
|
||||
<option value="allow" title="Allow insecure connections">allow (insecure)</option>
|
||||
<option value="prefer" title="Prefer secure connections">prefer (secure)</option>
|
||||
<option value="require" title="Require secure connections">require (secure)</option>
|
||||
<option value="verify-full" title="Verify full certificate">verify-full (secure)
|
||||
</option>
|
||||
</x-forms.select>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex flex-col py-2 w-64">
|
||||
<div class="flex items-center gap-2 pb-2">
|
||||
@@ -151,10 +79,12 @@
|
||||
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}"
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input type="number" placeholder="5432" disabled="{{ $isPublic }}"
|
||||
id="publicPort" label="Public Port" canGate="update" :canResource="$database" />
|
||||
<x-forms.input placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
<x-forms.input type="number" placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
</div>
|
||||
<x-forms.textarea label="Custom MongoDB Configuration" rows="10" id="mongoConf"
|
||||
canGate="update" :canResource="$database" />
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
<x-modal-input title="Resource Details" buttonTitle="Details">
|
||||
<livewire:project.shared.resource-details :resource="$database" />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
|
||||
@@ -56,81 +59,9 @@
|
||||
<x-forms.input placeholder="3000:5432" id="portsMappings" label="Ports Mappings"
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433" canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input label="MySQL URL (internal)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="db_url" />
|
||||
@if ($db_url_public)
|
||||
<x-forms.input label="MySQL URL (public)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="db_url_public" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<h3>SSL Configuration</h3>
|
||||
@if ($enableSsl && $certificateValidUntil)
|
||||
<x-modal-confirmation title="Regenerate SSL Certificates"
|
||||
buttonTitle="Regenerate SSL Certificates" :actions="[
|
||||
'The SSL certificate of this database will be regenerated.',
|
||||
'You must restart the database after regenerating the certificate to start using the new certificate.',
|
||||
]"
|
||||
submitAction="regenerateSslCertificate" :confirmWithText="false" :confirmWithPassword="false" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if ($enableSsl && $certificateValidUntil)
|
||||
<span class="text-sm">Valid until:
|
||||
@if (now()->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
|
||||
@elseif(now()->addDays(30)->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expiring
|
||||
soon</span>
|
||||
@else
|
||||
<span>{{ $certificateValidUntil->format('d.m.Y H:i:s') }}</span>
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="w-64">
|
||||
@if (str($database->status)->contains('exited'))
|
||||
<x-forms.checkbox id="enableSsl" label="Enable SSL"
|
||||
wire:model.live="enableSsl" instantSave="instantSaveSSL" canGate="update" :canResource="$database" />
|
||||
@else
|
||||
<x-forms.checkbox id="enableSsl" label="Enable SSL"
|
||||
wire:model.live="enableSsl" instantSave="instantSaveSSL" disabled
|
||||
helper="Database should be stopped to change this settings." />
|
||||
@endif
|
||||
</div>
|
||||
@if ($enableSsl)
|
||||
<div class="mx-2">
|
||||
@if (str($database->status)->contains('exited'))
|
||||
<x-forms.select id="sslMode" label="SSL Mode" wire:model.live="sslMode"
|
||||
instantSave="instantSaveSSL"
|
||||
helper="Choose the SSL verification mode for MySQL connections" canGate="update" :canResource="$database">
|
||||
<option value="PREFERRED" title="Prefer secure connections">Prefer (secure)</option>
|
||||
<option value="REQUIRED" title="Require secure connections">Require (secure)</option>
|
||||
<option value="VERIFY_CA" title="Verify CA certificate">Verify CA (secure)</option>
|
||||
<option value="VERIFY_IDENTITY" title="Verify full certificate">Verify Full (secure)
|
||||
</option>
|
||||
</x-forms.select>
|
||||
@else
|
||||
<x-forms.select id="sslMode" label="SSL Mode" instantSave="instantSaveSSL"
|
||||
disabled helper="Database should be stopped to change this settings.">
|
||||
<option value="PREFERRED" title="Prefer secure connections">Prefer (secure)</option>
|
||||
<option value="REQUIRED" title="Require secure connections">Require (secure)</option>
|
||||
<option value="VERIFY_CA" title="Verify CA certificate">Verify CA (secure)</option>
|
||||
<option value="VERIFY_IDENTITY" title="Verify full certificate">Verify Full (secure)
|
||||
</option>
|
||||
</x-forms.select>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<livewire:project.database.mysql.status-info :database="$database" />
|
||||
|
||||
<div>
|
||||
<div class="flex flex-col py-2 w-64">
|
||||
@@ -153,10 +84,12 @@
|
||||
</div>
|
||||
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}"
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input type="number" placeholder="5432" disabled="{{ $isPublic }}"
|
||||
id="publicPort" label="Public Port" canGate="update" :canResource="$database" />
|
||||
<x-forms.input placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
<x-forms.input type="number" placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
</div>
|
||||
<x-forms.textarea label="Custom Mysql Configuration" rows="10" id="mysqlConf" canGate="update" :canResource="$database" />
|
||||
<h3 class="pt-4">Advanced</h3>
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
<x-forms.button type="submit" canGate="update" :canResource="$database">
|
||||
Save
|
||||
</x-forms.button>
|
||||
<x-modal-input title="Resource Details" buttonTitle="Details">
|
||||
<livewire:project.shared.resource-details :resource="$database" />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
|
||||
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
|
||||
@@ -68,112 +71,38 @@
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
|
||||
<x-forms.input label="Postgres URL (internal)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="db_url" />
|
||||
@if ($db_url_public)
|
||||
<x-forms.input label="Postgres URL (public)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="db_url_public" />
|
||||
@endif
|
||||
</div>
|
||||
<livewire:project.database.postgresql.status-info :database="$database" />
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2 py-2">
|
||||
<h3>SSL Configuration</h3>
|
||||
@if ($enableSsl && $certificateValidUntil)
|
||||
<x-modal-confirmation title="Regenerate SSL Certificates" buttonTitle="Regenerate SSL Certificates"
|
||||
:actions="[
|
||||
'The SSL certificate of this database will be regenerated.',
|
||||
'You must restart the database after regenerating the certificate to start using the new certificate.',
|
||||
]" submitAction="regenerateSslCertificate" :confirmWithText="false"
|
||||
:confirmWithPassword="false" />
|
||||
<h3>Proxy</h3>
|
||||
<x-loading wire:loading wire:target="instantSave" />
|
||||
@if (data_get($database, 'is_public'))
|
||||
<x-slide-over fullScreen>
|
||||
<x-slot:title>Proxy Logs</x-slot:title>
|
||||
<x-slot:content>
|
||||
<livewire:project.shared.get-logs :server="$server" :resource="$database"
|
||||
container="{{ data_get($database, 'uuid') }}-proxy" :collapsible="false" lazy />
|
||||
</x-slot:content>
|
||||
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}"
|
||||
@click="slideOverOpen=true">Logs</x-forms.button>
|
||||
</x-slide-over>
|
||||
@endif
|
||||
</div>
|
||||
@if ($enableSsl && $certificateValidUntil)
|
||||
<span class="text-sm">Valid until:
|
||||
@if (now()->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
|
||||
@elseif(now()->addDays(30)->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expiring
|
||||
soon</span>
|
||||
@else
|
||||
<span>{{ $certificateValidUntil->format('d.m.Y H:i:s') }}</span>
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
<div class="flex flex-col gap-2 w-64">
|
||||
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input type="number" placeholder="5432" disabled="{{ $isPublic }}" id="publicPort"
|
||||
label="Public Port" canGate="update" :canResource="$database" />
|
||||
<x-forms.input type="number" placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="w-64" wire:key='enable_ssl'>
|
||||
@if ($database->isExited())
|
||||
<x-forms.checkbox id="enableSsl" label="Enable SSL" wire:model.live="enableSsl"
|
||||
instantSave="instantSaveSSL" canGate="update" :canResource="$database" />
|
||||
@else
|
||||
<x-forms.checkbox id="enableSsl" label="Enable SSL" wire:model.live="enableSsl"
|
||||
instantSave="instantSaveSSL" disabled
|
||||
helper="Database should be stopped to change this settings." />
|
||||
@endif
|
||||
</div>
|
||||
@if ($enableSsl)
|
||||
<div class="mx-2">
|
||||
@if ($database->isExited())
|
||||
<x-forms.select id="sslMode" label="SSL Mode" wire:model.live="sslMode"
|
||||
instantSave="instantSaveSSL"
|
||||
helper="Choose the SSL verification mode for PostgreSQL connections" canGate="update"
|
||||
:canResource="$database">
|
||||
<option value="allow" title="Allow insecure connections">allow (insecure)</option>
|
||||
<option value="prefer" title="Prefer secure connections">prefer (secure)</option>
|
||||
<option value="require" title="Require secure connections">require (secure)</option>
|
||||
<option value="verify-ca" title="Verify CA certificate">verify-ca (secure)</option>
|
||||
<option value="verify-full" title="Verify full certificate">verify-full (secure)
|
||||
</option>
|
||||
</x-forms.select>
|
||||
@else
|
||||
<x-forms.select id="sslMode" label="SSL Mode" instantSave="instantSaveSSL" disabled
|
||||
helper="Database should be stopped to change this settings.">
|
||||
<option value="allow" title="Allow insecure connections">allow (insecure)</option>
|
||||
<option value="prefer" title="Prefer secure connections">prefer (secure)</option>
|
||||
<option value="require" title="Require secure connections">require (secure)</option>
|
||||
<option value="verify-ca" title="Verify CA certificate">verify-ca (secure)</option>
|
||||
<option value="verify-full" title="Verify full certificate">verify-full (secure)
|
||||
</option>
|
||||
</x-forms.select>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2 py-2">
|
||||
<h3>Proxy</h3>
|
||||
<x-loading wire:loading wire:target="instantSave" />
|
||||
@if (data_get($database, 'is_public'))
|
||||
<x-slide-over fullScreen>
|
||||
<x-slot:title>Proxy Logs</x-slot:title>
|
||||
<x-slot:content>
|
||||
<livewire:project.shared.get-logs :server="$server" :resource="$database"
|
||||
container="{{ data_get($database, 'uuid') }}-proxy" :collapsible="false" lazy />
|
||||
</x-slot:content>
|
||||
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}"
|
||||
@click="slideOverOpen=true">Logs</x-forms.button>
|
||||
</x-slide-over>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 w-64">
|
||||
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort"
|
||||
label="Public Port" canGate="update" :canResource="$database" />
|
||||
<x-forms.input placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.textarea label="Custom PostgreSQL Configuration" rows="10" id="postgresConf"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
</div>
|
||||
<x-forms.textarea label="Custom PostgreSQL Configuration" rows="10" id="postgresConf"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<x-forms.button type="submit" canGate="update" :canResource="$database">
|
||||
Save
|
||||
</x-forms.button>
|
||||
<x-modal-input title="Resource Details" buttonTitle="Details">
|
||||
<livewire:project.shared.resource-details :resource="$database" />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
|
||||
@@ -60,56 +63,8 @@
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input label="Redis URL (internal)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="dbUrl" canGate="update" :canResource="$database" />
|
||||
@if ($dbUrlPublic)
|
||||
<x-forms.input label="Redis URL (public)"
|
||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||
type="password" readonly wire:model="dbUrlPublic" canGate="update" :canResource="$database" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<h3>SSL Configuration</h3>
|
||||
@if ($enableSsl && $certificateValidUntil)
|
||||
<x-modal-confirmation title="Regenerate SSL Certificates"
|
||||
buttonTitle="Regenerate SSL Certificates" :actions="[
|
||||
'The SSL certificate of this database will be regenerated.',
|
||||
'You must restart the database after regenerating the certificate to start using the new certificate.',
|
||||
]"
|
||||
submitAction="regenerateSslCertificate" :confirmWithText="false" :confirmWithPassword="false" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@if ($enableSsl && $certificateValidUntil)
|
||||
<span class="text-sm">Valid until:
|
||||
@if (now()->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
|
||||
@elseif(now()->addDays(30)->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expiring
|
||||
soon</span>
|
||||
@else
|
||||
<span>{{ $certificateValidUntil->format('d.m.Y H:i:s') }}</span>
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="w-64" wire:key='enable_ssl'>
|
||||
@if (str($database->status)->contains('exited'))
|
||||
<x-forms.checkbox id="enableSsl" label="Enable SSL"
|
||||
wire:model.live="enableSsl" instantSave="instantSaveSSL" canGate="update"
|
||||
:canResource="$database" />
|
||||
@else
|
||||
<x-forms.checkbox id="enableSsl" label="Enable SSL"
|
||||
wire:model.live="enableSsl" instantSave="instantSaveSSL" disabled
|
||||
helper="Database should be stopped to change this settings." canGate="update"
|
||||
:canResource="$database" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<livewire:project.database.redis.status-info :database="$database" />
|
||||
<div>
|
||||
<div class="flex flex-col py-2 w-64">
|
||||
<div class="flex items-center gap-2 pb-2">
|
||||
@@ -132,10 +87,12 @@
|
||||
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available"
|
||||
canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}"
|
||||
id="publicPort" label="Public Port" canGate="update" :canResource="$database" />
|
||||
<x-forms.input placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input type="number" placeholder="5432" disabled="{{ $isPublic }}"
|
||||
id="publicPort" label="Public Port" canGate="update" :canResource="$database" />
|
||||
<x-forms.input type="number" placeholder="3600" disabled="{{ $isPublic }}" id="publicPortTimeout"
|
||||
label="Proxy Timeout (seconds)" helper="Timeout for the public TCP proxy connection in seconds. Default: 3600 (1 hour)." canGate="update" :canResource="$database" />
|
||||
</div>
|
||||
</div>
|
||||
<x-forms.textarea placeholder="# maxmemory 256mb
|
||||
# maxmemory-policy allkeys-lru
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<div>
|
||||
<x-database-status-info :database="$database" :label="$label" :db-url="$dbUrl" :db-url-public="$dbUrlPublic"
|
||||
:enable-ssl="$enableSsl" :ssl-mode="$sslMode" :certificate-valid-until="$certificateValidUntil"
|
||||
:supports-ssl="$supportsSsl" :ssl-mode-options="$sslModeOptions" :ssl-mode-helper="$sslModeHelper"
|
||||
:show-public-url-placeholder="$showPublicUrlPlaceholder" :is-exited="$isExited" />
|
||||
</div>
|
||||
@@ -2,7 +2,7 @@
|
||||
<x-slot:title>
|
||||
Projects | Coolify
|
||||
</x-slot>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex gap-2 items-center">
|
||||
<h1>Projects</h1>
|
||||
@can('createAnyResource')
|
||||
<x-modal-input buttonTitle="+ Add" title="New Project">
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<x-forms.input id="branch" required label="Branch" />
|
||||
<x-forms.select wire:model.live="build_pack" label="Build Pack" required>
|
||||
<option value="nixpacks">Nixpacks</option>
|
||||
<option value="railpack">Railpack (Beta)</option>
|
||||
<option value="static">Static</option>
|
||||
<option value="dockerfile">Dockerfile</option>
|
||||
<option value="dockercompose">Docker Compose</option>
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
<livewire:source.github.create />
|
||||
</x-modal-input>
|
||||
@if ($repositories->count() > 0)
|
||||
<a target="_blank" class="flex hover:no-underline" href="{{ getInstallationPath($github_app) }}">
|
||||
<x-forms.button>
|
||||
Change Repositories on GitHub
|
||||
<x-external-link />
|
||||
</x-forms.button>
|
||||
<x-forms.button wire:click.prevent="loadRepositories({{ $github_app->id }})">
|
||||
Refresh Repository List
|
||||
</x-forms.button>
|
||||
<a target="_blank" class="inline-flex items-center self-center gap-1 text-sm hover:underline dark:text-neutral-400"
|
||||
href="{{ getInstallationPath($github_app) }}">
|
||||
Change Repositories on GitHub
|
||||
<x-external-link />
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
@@ -51,7 +53,10 @@
|
||||
@endforeach
|
||||
</x-forms.datalist>
|
||||
</div>
|
||||
<x-forms.button wire:click.prevent="loadBranches"> Load Repository </x-forms.button>
|
||||
<x-forms.button :showLoadingIndicator="false" wire:click.prevent="loadBranches" wire:target="loadBranches, selected_repository_id">
|
||||
Load Repository
|
||||
<x-loading-on-button wire:loading.delay wire:target="loadBranches, selected_repository_id" />
|
||||
</x-forms.button>
|
||||
</div>
|
||||
@else
|
||||
<div>No repositories found. Check your GitHub App configuration.</div>
|
||||
@@ -78,6 +83,7 @@
|
||||
</x-forms.select>
|
||||
<x-forms.select wire:model.live="build_pack" label="Build Pack" required>
|
||||
<option value="nixpacks">Nixpacks</option>
|
||||
<option value="railpack">Railpack (Beta)</option>
|
||||
<option value="static">Static</option>
|
||||
<option value="dockerfile">Dockerfile</option>
|
||||
<option value="dockercompose">Docker Compose</option>
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
@endif
|
||||
<x-forms.select wire:model.live="build_pack" label="Build Pack" required>
|
||||
<option value="nixpacks">Nixpacks</option>
|
||||
<option value="railpack">Railpack (Beta)</option>
|
||||
<option value="static">Static</option>
|
||||
<option value="dockerfile">Dockerfile</option>
|
||||
<option value="dockercompose">Docker Compose</option>
|
||||
|
||||
@@ -173,6 +173,34 @@
|
||||
</template>
|
||||
</x-slot:logo>
|
||||
</x-resource-view>
|
||||
<template x-if="service.amd_only">
|
||||
<div class="absolute top-2 right-10 group">
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs rounded bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200 cursor-pointer">
|
||||
AMD only
|
||||
</span>
|
||||
<div class="info-helper-popup right-0 w-sm">
|
||||
<div class="p-4">
|
||||
This service only supports AMD64/x86_64 architecture. It will not work
|
||||
on ARM-based servers (e.g., Apple Silicon, Raspberry Pi, AWS Graviton).
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="service.arm_only">
|
||||
<div class="absolute top-2 right-10 group">
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs rounded-full bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200 cursor-pointer">
|
||||
ARM only
|
||||
</span>
|
||||
<div class="info-helper-popup right-0 w-sm">
|
||||
<div class="p-4">
|
||||
This service only supports ARM64/aarch64 architecture. It will not work
|
||||
on AMD64/x86_64-based servers.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="shouldShowDocIcon(service)">
|
||||
<a :href="getDocLink(service) || coolifyDocsUrl(service.name)" target="_blank"
|
||||
@click.stop @mouseenter="resolveDocLink(service)"
|
||||
@@ -424,6 +452,7 @@
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="font-bold dark:group-hover:text-white">
|
||||
Swarm Docker <span class="text-xs">({{ $swarmDocker->name }})</span>
|
||||
<x-deprecated-badge />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
href="{{ route('project.service.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">Environment Variables</span></a>
|
||||
<a class='sub-menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||
href="{{ route('project.service.storages', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">Persistent Storages</span></a>
|
||||
<a class='sub-menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||
<a @class(['sub-menu-item', 'menu-item-active' => str($currentRoute)->startsWith('project.service.scheduled-tasks')]) {{ wireNavigate() }}
|
||||
href="{{ route('project.service.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">Scheduled Tasks</span></a>
|
||||
<a class='sub-menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||
href="{{ route('project.service.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}"><span class="menu-item-label">Webhooks</span></a>
|
||||
@@ -43,134 +43,12 @@
|
||||
@endif
|
||||
|
||||
@foreach ($applications as $application)
|
||||
<div @class([
|
||||
'border-l border-dashed border-red-500' => str(
|
||||
$application->status)->contains(['exited']),
|
||||
'border-l border-dashed border-success' => str(
|
||||
$application->status)->contains(['running']),
|
||||
'border-l border-dashed border-warning' => str(
|
||||
$application->status)->contains(['starting']),
|
||||
'flex gap-2 box-without-bg-without-border dark:bg-coolgray-100 bg-white dark:hover:text-neutral-300 group',
|
||||
])>
|
||||
<div class="flex flex-row w-full">
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="pb-2">
|
||||
@if ($application->human_name)
|
||||
{{ Str::headline($application->human_name) }}
|
||||
@else
|
||||
{{ Str::headline($application->name) }}
|
||||
@endif
|
||||
<span class="text-xs">({{ $application->image }})</span>
|
||||
</div>
|
||||
@if ($application->configuration_required)
|
||||
<span class="text-xs text-error">(configuration required)</span>
|
||||
@endif
|
||||
@if ($application->description)
|
||||
<span class="text-xs">{{ Str::limit($application->description, 60) }}</span>
|
||||
@endif
|
||||
@if ($application->fqdn)
|
||||
<span class="flex gap-1 text-xs">{{ Str::limit($application->fqdn, 60) }}
|
||||
@can('update', $service)
|
||||
<x-modal-input title="Edit Domains" :closeOutside="false">
|
||||
<x-slot:content>
|
||||
<span class="cursor-pointer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-4 h-4 dark:text-warning text-coollabs"
|
||||
viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path
|
||||
d="m12 15l8.385-8.415a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zm4-10l3 3" />
|
||||
<path d="M9 7.07A7 7 0 0 0 10 21a7 7 0 0 0 6.929-6" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
</span>
|
||||
</x-slot:content>
|
||||
<livewire:project.service.edit-domain
|
||||
applicationId="{{ $application->id }}"
|
||||
wire:key="edit-domain-{{ $application->id }}" />
|
||||
</x-modal-input>
|
||||
@endcan
|
||||
</span>
|
||||
@endif
|
||||
<div class="pt-2 text-xs">{{ formatContainerStatus($application->status) }}</div>
|
||||
</div>
|
||||
<div class="flex items-center px-4">
|
||||
<a class="mx-4 text-xs font-bold hover:underline" {{ wireNavigate() }}
|
||||
href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $application->uuid]) }}">
|
||||
Settings
|
||||
</a>
|
||||
@if (str($application->status)->contains('running'))
|
||||
@can('update', $service)
|
||||
<x-modal-confirmation title="Confirm Service Application Restart?"
|
||||
buttonTitle="Restart"
|
||||
submitAction="restartApplication({{ $application->id }})" :actions="[
|
||||
'The selected service application will be unavailable during the restart.',
|
||||
'If the service application is currently in use data could be lost.',
|
||||
]"
|
||||
:confirmWithText="false" :confirmWithPassword="false"
|
||||
step2ButtonText="Restart Service Container" />
|
||||
@endcan
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<livewire:project.service.resource-card :service="$service" :resource="$application"
|
||||
:parameters="$parameters" wire:key="service-application-card-{{ $application->id }}" />
|
||||
@endforeach
|
||||
@foreach ($databases as $database)
|
||||
<div @class([
|
||||
'border-l border-dashed border-red-500' => str($database->status)->contains(
|
||||
['exited']),
|
||||
'border-l border-dashed border-success' => str($database->status)->contains(
|
||||
['running']),
|
||||
'border-l border-dashed border-warning' => str($database->status)->contains(
|
||||
['restarting']),
|
||||
'flex gap-2 box-without-bg-without-border dark:bg-coolgray-100 bg-white dark:hover:text-neutral-300 group',
|
||||
])>
|
||||
<div class="flex flex-row w-full">
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="pb-2">
|
||||
@if ($database->human_name)
|
||||
{{ Str::headline($database->human_name) }}
|
||||
@else
|
||||
{{ Str::headline($database->name) }}
|
||||
@endif
|
||||
<span class="text-xs">({{ $database->image }})</span>
|
||||
</div>
|
||||
@if ($database->configuration_required)
|
||||
<span class="text-xs text-error">(configuration required)</span>
|
||||
@endif
|
||||
@if ($database->description)
|
||||
<span class="text-xs">{{ Str::limit($database->description, 60) }}</span>
|
||||
@endif
|
||||
<div class="text-xs">{{ formatContainerStatus($database->status) }}</div>
|
||||
</div>
|
||||
<div class="flex items-center px-4">
|
||||
@if ($database->isBackupSolutionAvailable() || $database->is_migrated)
|
||||
<a class="mx-4 text-xs font-bold hover:underline" {{ wireNavigate() }}
|
||||
href="{{ route('project.service.database.backups', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $database->uuid]) }}">
|
||||
Backups
|
||||
</a>
|
||||
@endif
|
||||
<a class="mx-4 text-xs font-bold hover:underline" {{ wireNavigate() }}
|
||||
href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $database->uuid]) }}">
|
||||
Settings
|
||||
</a>
|
||||
@if (str($database->status)->contains('running'))
|
||||
@can('update', $service)
|
||||
<x-modal-confirmation title="Confirm Service Database Restart?"
|
||||
buttonTitle="Restart" submitAction="restartDatabase({{ $database->id }})"
|
||||
:actions="[
|
||||
'This service database will be unavailable during the restart.',
|
||||
'If the service database is currently in use data could be lost.',
|
||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step2ButtonText="Restart Database" />
|
||||
@endcan
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<livewire:project.service.resource-card :service="$service" :resource="$database"
|
||||
:parameters="$parameters" wire:key="service-database-card-{{ $database->id }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@elseif ($currentRoute === 'project.service.environment-variables')
|
||||
@@ -189,6 +67,8 @@
|
||||
@endforeach
|
||||
@elseif ($currentRoute === 'project.service.scheduled-tasks.show')
|
||||
<livewire:project.shared.scheduled-task.all :resource="$service" />
|
||||
@elseif ($currentRoute === 'project.service.scheduled-tasks')
|
||||
<livewire:project.shared.scheduled-task.show />
|
||||
@elseif ($currentRoute === 'project.service.webhooks')
|
||||
<livewire:project.shared.webhooks :resource="$service" />
|
||||
@elseif ($currentRoute === 'project.service.resource-operations')
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<div class="w-full">
|
||||
<form wire:submit.prevent='submit' class="flex flex-col w-full gap-2">
|
||||
@if($requiredPort)
|
||||
<x-callout type="warning" title="Required Port: {{ $requiredPort }}" class="mb-2">
|
||||
<x-callout type="info" title="Required Port: {{ $requiredPort }}" class="mb-2">
|
||||
This service requires port <strong>{{ $requiredPort }}</strong> to function correctly. All domains must include this port number (or any other port if you know what you're doing).
|
||||
<br><br>
|
||||
<strong>Example:</strong> http://app.coolify.io:{{ $requiredPort }}
|
||||
<strong>Example:</strong> https://app.coolify.io:{{ $requiredPort }},https://www.app.coolify.io:{{ $requiredPort }}
|
||||
</x-callout>
|
||||
@endif
|
||||
|
||||
<x-forms.input canGate="update" :canResource="$application" placeholder="https://app.coolify.io" label="Domains"
|
||||
id="fqdn"
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input>
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."></x-forms.input>
|
||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<div>
|
||||
<div class="flex flex-col gap-4 p-4 bg-white border dark:bg-base dark:border-coolgray-300 border-neutral-200">
|
||||
@if ($isReadOnly)
|
||||
@if ($fileStorage->is_too_large)
|
||||
<div class="w-full p-2 text-sm rounded bg-warning/10 text-warning">
|
||||
File on server exceeds 5 MB and cannot be edited from the UI. Edit it directly on the server.
|
||||
</div>
|
||||
@elseif ($isReadOnly)
|
||||
<div class="w-full p-2 text-sm rounded bg-warning/10 text-warning">
|
||||
@if ($fileStorage->is_directory)
|
||||
This directory is mounted as read-only and cannot be modified from the UI.
|
||||
@@ -17,7 +21,7 @@
|
||||
</div>
|
||||
@if ($resource instanceof \App\Models\Application)
|
||||
@can('update', $resource)
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox instantSave canGate="update" :canResource="$resource" label="Add suffix for PR deployments"
|
||||
id="isPreviewSuffixEnabled"
|
||||
helper="When enabled, a -pr-N suffix is added to this volume's path for preview deployments (e.g. ./scripts becomes ./scripts-pr-1). Disable this for volumes that contain shared config or scripts from your repository."></x-forms.checkbox>
|
||||
@@ -44,7 +48,7 @@
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
|
||||
shortConfirmationLabel="Filepath" />
|
||||
@else
|
||||
@if (!$fileStorage->is_binary)
|
||||
@if (!$fileStorage->is_binary && !$fileStorage->is_too_large)
|
||||
<x-modal-confirmation :ignoreWire="false" title="Confirm File Conversion to Directory?"
|
||||
buttonTitle="Convert to directory" submitAction="convertToDirectory" :actions="[
|
||||
'The selected file will be permanently deleted and an empty directory will be created in its place.',
|
||||
@@ -67,7 +71,7 @@
|
||||
@if (!$fileStorage->is_directory)
|
||||
@can('update', $resource)
|
||||
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox instantSave label="Is this based on the Git repository?"
|
||||
id="isBasedOnGit"></x-forms.checkbox>
|
||||
</div>
|
||||
@@ -76,13 +80,13 @@
|
||||
label="{{ $fileStorage->is_based_on_git ? 'Content (refreshed after a successful deployment)' : 'Content' }}"
|
||||
helper="The content shown may be outdated. Click 'Load from server' to fetch the latest version."
|
||||
rows="20" id="content"
|
||||
readonly="{{ $fileStorage->is_based_on_git || $fileStorage->is_binary }}"></x-forms.textarea>
|
||||
@if (!$fileStorage->is_based_on_git && !$fileStorage->is_binary)
|
||||
readonly="{{ $fileStorage->is_based_on_git || $fileStorage->is_binary || $fileStorage->is_too_large }}"></x-forms.textarea>
|
||||
@if (!$fileStorage->is_based_on_git && !$fileStorage->is_binary && !$fileStorage->is_too_large)
|
||||
<x-forms.button class="w-full" type="submit">Save</x-forms.button>
|
||||
@endif
|
||||
@else
|
||||
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox disabled label="Is this based on the Git repository?"
|
||||
id="isBasedOnGit"></x-forms.checkbox>
|
||||
</div>
|
||||
@@ -103,7 +107,7 @@
|
||||
</div>
|
||||
@endcan
|
||||
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox disabled label="Is this based on the Git repository?"
|
||||
id="isBasedOnGit"></x-forms.checkbox>
|
||||
</div>
|
||||
|
||||
@@ -9,120 +9,198 @@
|
||||
<h1>{{ $title }}</h1>
|
||||
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
||||
<div class="navbar-main" x-data">
|
||||
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
||||
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
<nav
|
||||
class="scrollbar flex min-h-10 w-full flex-nowrap items-center gap-6 overflow-x-scroll overflow-y-hidden pb-1 whitespace-nowrap md:w-auto md:overflow-visible">
|
||||
<a class="shrink-0 {{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
href="{{ route('project.service.configuration', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.service.logs') ? 'dark:text-white' : '' }}"
|
||||
<a class="shrink-0 {{ request()->routeIs('project.service.logs') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('project.service.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
</a>
|
||||
@can('canAccessTerminal')
|
||||
<a class="{{ request()->routeIs('project.service.command') ? 'dark:text-white' : '' }}"
|
||||
<a class="shrink-0 {{ request()->routeIs('project.service.command') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('project.service.command', $parameters) }}">
|
||||
<button>Terminal</button>
|
||||
</a>
|
||||
@endcan
|
||||
<x-services.links :service="$service" />
|
||||
<div class="shrink-0">
|
||||
<x-services.links :service="$service" />
|
||||
</div>
|
||||
</nav>
|
||||
@if ($service->isDeployable)
|
||||
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
||||
<x-services.advanced :service="$service" />
|
||||
@if (str($service->status)->contains('running'))
|
||||
<x-forms.button title="Restart" @click="$wire.dispatch('restartEvent')">
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M19.933 13.041 a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Restart
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<div class="order-first flex flex-wrap items-center gap-2 sm:order-last">
|
||||
<div class="md:hidden">
|
||||
<x-dropdown>
|
||||
<x-slot:title>
|
||||
Actions
|
||||
</x-slot>
|
||||
@if (str($service->status)->contains('running'))
|
||||
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('restartEvent')">
|
||||
Restart
|
||||
</div>
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:trigger>
|
||||
<div class="dropdown-item dropdown-item-touch text-error">
|
||||
Stop
|
||||
</div>
|
||||
</x-slot:trigger>
|
||||
</x-modal-confirmation>
|
||||
<div class="mx-2 my-1 border-t border-neutral-200 dark:border-coolgray-300"></div>
|
||||
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('pullAndRestartEvent')">
|
||||
Pull Latest Images & Restart
|
||||
</div>
|
||||
@elseif (str($service->status)->contains('degraded'))
|
||||
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('restartEvent')">
|
||||
Restart
|
||||
</div>
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:trigger>
|
||||
<div class="dropdown-item dropdown-item-touch text-error">
|
||||
Stop
|
||||
</div>
|
||||
</x-slot:trigger>
|
||||
</x-modal-confirmation>
|
||||
<div class="mx-2 my-1 border-t border-neutral-200 dark:border-coolgray-300"></div>
|
||||
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('forceDeployEvent')">
|
||||
Force Restart
|
||||
</div>
|
||||
@elseif (str($service->status)->contains('exited'))
|
||||
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('startEvent')">
|
||||
Deploy
|
||||
</div>
|
||||
<div class="mx-2 my-1 border-t border-neutral-200 dark:border-coolgray-300"></div>
|
||||
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('forceDeployEvent')">
|
||||
Force Deploy
|
||||
</div>
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='stop(true)'>
|
||||
Force Cleanup Containers
|
||||
</div>
|
||||
@else
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:trigger>
|
||||
<div class="dropdown-item dropdown-item-touch text-error">
|
||||
Stop
|
||||
</div>
|
||||
</x-slot:trigger>
|
||||
</x-modal-confirmation>
|
||||
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('startEvent')">
|
||||
Deploy
|
||||
</div>
|
||||
<div class="mx-2 my-1 border-t border-neutral-200 dark:border-coolgray-300"></div>
|
||||
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('forceDeployEvent')">
|
||||
Force Deploy
|
||||
</div>
|
||||
<div class="dropdown-item dropdown-item-touch" wire:click='stop(true)'>
|
||||
Force Cleanup Containers
|
||||
</div>
|
||||
@endif
|
||||
</x-dropdown>
|
||||
</div>
|
||||
<div class="hidden flex-wrap items-center gap-2 md:flex">
|
||||
<x-services.advanced :service="$service" />
|
||||
@if (str($service->status)->contains('running'))
|
||||
<x-forms.button title="Restart" @click="$wire.dispatch('restartEvent')">
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M19.933 13.041 a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
@elseif (str($service->status)->contains('degraded'))
|
||||
<x-forms.button title="Restart" @click="$wire.dispatch('restartEvent')">
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Restart
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
Restart
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
@elseif (str($service->status)->contains('degraded'))
|
||||
<x-forms.button title="Restart" @click="$wire.dispatch('restartEvent')">
|
||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
@elseif (str($service->status)->contains('exited'))
|
||||
<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?" buttonTitle="Stop" :dispatchEvent="true"
|
||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
Restart
|
||||
</x-forms.button>
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
@elseif (str($service->status)->contains('exited'))
|
||||
<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>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
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>
|
||||
@endif
|
||||
Deploy
|
||||
</button>
|
||||
@else
|
||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||
<x-slot:button-title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
<path
|
||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||
</path>
|
||||
</svg>
|
||||
Stop
|
||||
</x-slot:button-title>
|
||||
</x-modal-confirmation>
|
||||
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||
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>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
</svg>
|
||||
<span class="menu-item-label">Back</span>
|
||||
</a>
|
||||
<a class="sub-menu-item menu-item-active" href="#"><span class="menu-item-label">General</span></a>
|
||||
<a class="sub-menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||
href="{{ route('project.service.index', $parameters) }}"><span class="menu-item-label">General</span></a>
|
||||
<a class="sub-menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||
href="{{ route('project.service.index.advanced', $parameters) }}"><span class="menu-item-label">Advanced</span></a>
|
||||
</div>
|
||||
@endif
|
||||
<div class="w-full">
|
||||
@@ -23,63 +26,9 @@
|
||||
{{ data_get_str($service, 'name')->limit(10) }} >
|
||||
{{ data_get_str($serviceApplication, 'name')->limit(10) }} | Coolify
|
||||
</x-slot>
|
||||
<form wire:submit='submitApplication'>
|
||||
<div class="flex items-center gap-2 pb-4">
|
||||
@if ($serviceApplication->human_name)
|
||||
<h2>{{ Str::headline($serviceApplication->human_name) }}</h2>
|
||||
@else
|
||||
<h2>{{ Str::headline($serviceApplication->name) }}</h2>
|
||||
@endif
|
||||
<x-forms.button canGate="update" :canResource="$serviceApplication" type="submit">Save</x-forms.button>
|
||||
@can('update', $serviceApplication)
|
||||
<x-modal-confirmation wire:click="convertToDatabase" title="Convert to Database"
|
||||
buttonTitle="Convert to Database" submitAction="convertToDatabase" :actions="['The selected resource will be converted to a service database.']"
|
||||
confirmationText="{{ Str::headline($serviceApplication->name) }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below"
|
||||
shortConfirmationLabel="Service Application Name" />
|
||||
@endcan
|
||||
@can('delete', $serviceApplication)
|
||||
<x-modal-confirmation title="Confirm Service Application Deletion?" buttonTitle="Delete" isErrorButton
|
||||
submitAction="deleteApplication" :actions="['The selected service application container will be stopped and permanently deleted.']"
|
||||
confirmationText="{{ Str::headline($serviceApplication->name) }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below"
|
||||
shortConfirmationLabel="Service Application Name" />
|
||||
@endcan
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
@if ($requiredPort && !$serviceApplication->serviceType()?->contains(str($serviceApplication->image)->before(':')))
|
||||
<x-callout type="warning" title="Required Port: {{ $requiredPort }}" class="mb-2">
|
||||
This service requires port <strong>{{ $requiredPort }}</strong> to function correctly. All domains must include this port number (or any other port if you know what you're doing).
|
||||
<br><br>
|
||||
<strong>Example:</strong> http://app.coolify.io:{{ $requiredPort }}
|
||||
</x-callout>
|
||||
@endif
|
||||
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$serviceApplication" label="Name" id="humanName"
|
||||
placeholder="Human readable name"></x-forms.input>
|
||||
<x-forms.input canGate="update" :canResource="$serviceApplication" label="Description"
|
||||
id="description"></x-forms.input>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@if (!$serviceApplication->serviceType()?->contains(str($serviceApplication->image)->before(':')))
|
||||
@if ($serviceApplication->required_fqdn)
|
||||
<x-forms.input canGate="update" :canResource="$serviceApplication" required placeholder="https://app.coolify.io"
|
||||
label="Domains" id="fqdn"
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input>
|
||||
@else
|
||||
<x-forms.input canGate="update" :canResource="$serviceApplication" placeholder="https://app.coolify.io"
|
||||
label="Domains" id="fqdn"
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input>
|
||||
@endif
|
||||
@endif
|
||||
<x-forms.input canGate="update" :canResource="$serviceApplication"
|
||||
helper="You can change the image you would like to deploy.<br><br><span class='dark:text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>"
|
||||
label="Image" id="image"></x-forms.input>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="py-2 pt-4">Advanced</h3>
|
||||
<div class="w-96 flex flex-col gap-1">
|
||||
@if ($currentRoute === 'project.service.index.advanced')
|
||||
<h2>Advanced</h2>
|
||||
<div class="w-full sm:w-96 flex flex-col gap-1 pt-4">
|
||||
@if (str($serviceApplication->image)->contains('pocketbase'))
|
||||
<x-forms.checkbox canGate="update" :canResource="$serviceApplication" instantSave="instantSaveApplicationSettings" id="isGzipEnabled"
|
||||
label="Enable Gzip Compression"
|
||||
@@ -99,77 +48,134 @@
|
||||
helper="Drain logs to your configured log drain endpoint in your Server settings."
|
||||
instantSave="instantSaveApplicationAdvanced" id="isLogDrainEnabled" label="Drain Logs" />
|
||||
</div>
|
||||
</form>
|
||||
@else
|
||||
<form wire:submit='submitApplication'>
|
||||
<div class="flex items-center gap-2 pb-4">
|
||||
@if ($serviceApplication->human_name)
|
||||
<h2>{{ Str::headline($serviceApplication->human_name) }}</h2>
|
||||
@else
|
||||
<h2>{{ Str::headline($serviceApplication->name) }}</h2>
|
||||
@endif
|
||||
<x-forms.button canGate="update" :canResource="$serviceApplication" type="submit">Save</x-forms.button>
|
||||
@can('update', $serviceApplication)
|
||||
<x-modal-confirmation wire:click="convertToDatabase" title="Convert to Database"
|
||||
buttonTitle="Convert to Database" submitAction="convertToDatabase" :actions="['The selected resource will be converted to a service database.']"
|
||||
confirmationText="{{ Str::headline($serviceApplication->name) }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below"
|
||||
shortConfirmationLabel="Service Application Name" />
|
||||
@endcan
|
||||
@can('delete', $serviceApplication)
|
||||
<x-modal-confirmation title="Confirm Service Application Deletion?" buttonTitle="Delete" isErrorButton
|
||||
submitAction="deleteApplication" :actions="['The selected service application container will be stopped and permanently deleted.']"
|
||||
confirmationText="{{ Str::headline($serviceApplication->name) }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below"
|
||||
shortConfirmationLabel="Service Application Name" />
|
||||
@endcan
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
@if ($requiredPort && !$serviceApplication->serviceType()?->contains(str($serviceApplication->image)->before(':')))
|
||||
<x-callout type="info" title="Required Port: {{ $requiredPort }}" class="mb-2">
|
||||
This service requires port <strong>{{ $requiredPort }}</strong> to function correctly. All domains must include this port number (or any other port if you know what you're doing).
|
||||
<br><br>
|
||||
<strong>Example:</strong> https://app.coolify.io:{{ $requiredPort }},https://www.app.coolify.io:{{ $requiredPort }}
|
||||
</x-callout>
|
||||
@endif
|
||||
|
||||
<x-domain-conflict-modal
|
||||
:conflicts="$domainConflicts"
|
||||
:showModal="$showDomainConflictModal"
|
||||
confirmAction="confirmDomainUsage">
|
||||
<x-slot:consequences>
|
||||
<ul class="mt-2 ml-4 list-disc">
|
||||
<li>Only one service will be accessible at this domain</li>
|
||||
<li>The routing behavior will be unpredictable</li>
|
||||
<li>You may experience service disruptions</li>
|
||||
<li>SSL certificates might not work correctly</li>
|
||||
</ul>
|
||||
</x-slot:consequences>
|
||||
</x-domain-conflict-modal>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$serviceApplication" label="Name" id="humanName"
|
||||
placeholder="Human readable name"></x-forms.input>
|
||||
<x-forms.input canGate="update" :canResource="$serviceApplication" label="Description"
|
||||
id="description"></x-forms.input>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@if (!$serviceApplication->serviceType()?->contains(str($serviceApplication->image)->before(':')))
|
||||
@if ($serviceApplication->required_fqdn)
|
||||
<x-forms.input canGate="update" :canResource="$serviceApplication" required placeholder="https://app.coolify.io"
|
||||
label="Domains" id="fqdn"
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."></x-forms.input>
|
||||
@else
|
||||
<x-forms.input canGate="update" :canResource="$serviceApplication" placeholder="https://app.coolify.io"
|
||||
label="Domains" id="fqdn"
|
||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."></x-forms.input>
|
||||
@endif
|
||||
@endif
|
||||
<x-forms.input canGate="update" :canResource="$serviceApplication"
|
||||
helper="You can change the image you would like to deploy.<br><br><span class='dark:text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>"
|
||||
label="Image" id="image"></x-forms.input>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@if ($showPortWarningModal)
|
||||
<div x-data="{ modalOpen: true }" x-init="$nextTick(() => { modalOpen = true })"
|
||||
@keydown.escape.window="modalOpen = false; $wire.call('cancelRemovePort')"
|
||||
:class="{ 'z-40': modalOpen }" class="relative w-auto h-auto">
|
||||
<template x-teleport="body">
|
||||
<div x-show="modalOpen"
|
||||
class="fixed top-0 lg:pt-10 left-0 z-99 flex items-start justify-center w-screen h-screen" x-cloak>
|
||||
<div x-show="modalOpen" class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
|
||||
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave="ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||
class="relative w-full py-6 border rounded-sm min-w-full lg:min-w-[36rem] max-w-[48rem] bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
|
||||
<div class="flex justify-between items-center pb-3">
|
||||
<h2 class="pr-8 font-bold">Remove Required Port?</h2>
|
||||
<button @click="modalOpen = false; $wire.call('cancelRemovePort')"
|
||||
class="flex absolute top-2 right-2 justify-center items-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
|
||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative w-auto">
|
||||
<x-callout type="warning" title="Port Requirement Warning" class="mb-4">
|
||||
This service requires port <strong>{{ $requiredPort }}</strong> to function correctly.
|
||||
One or more of your domains are missing a port number.
|
||||
</x-callout>
|
||||
<x-domain-conflict-modal
|
||||
:conflicts="$domainConflicts"
|
||||
:showModal="$showDomainConflictModal"
|
||||
confirmAction="confirmDomainUsage">
|
||||
<x-slot:consequences>
|
||||
<ul class="mt-2 ml-4 list-disc">
|
||||
<li>Only one service will be accessible at this domain</li>
|
||||
<li>The routing behavior will be unpredictable</li>
|
||||
<li>You may experience service disruptions</li>
|
||||
<li>SSL certificates might not work correctly</li>
|
||||
</ul>
|
||||
</x-slot:consequences>
|
||||
</x-domain-conflict-modal>
|
||||
|
||||
<x-callout type="danger" title="What will happen if you continue?" class="mb-4">
|
||||
<ul class="mt-2 ml-4 list-disc">
|
||||
<li>The service may become unreachable</li>
|
||||
<li>The proxy may not be able to route traffic correctly</li>
|
||||
<li>Environment variables may not be generated properly</li>
|
||||
<li>The service may fail to start or function</li>
|
||||
</ul>
|
||||
</x-callout>
|
||||
@if ($showPortWarningModal)
|
||||
<div x-data="{ modalOpen: true }" x-init="$nextTick(() => { modalOpen = true })"
|
||||
@keydown.escape.window="modalOpen = false; $wire.call('cancelRemovePort')"
|
||||
:class="{ 'z-40': modalOpen }" class="relative">
|
||||
<template x-teleport="body">
|
||||
<div x-show="modalOpen"
|
||||
class="fixed top-0 lg:pt-10 left-0 z-99 flex items-start justify-center w-screen h-screen" x-cloak>
|
||||
<div x-show="modalOpen" class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
|
||||
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave="ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||
class="relative w-full py-6 border rounded-sm min-w-full lg:min-w-[36rem] max-w-[48rem] bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
|
||||
<div class="flex justify-between items-center pb-3">
|
||||
<h2 class="pr-8 font-bold">Remove Required Port?</h2>
|
||||
<button @click="modalOpen = false; $wire.call('cancelRemovePort')"
|
||||
class="flex absolute top-2 right-2 justify-center items-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
|
||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative w-auto">
|
||||
<x-callout type="warning" title="Port Requirement Warning" class="mb-4">
|
||||
This service requires port <strong>{{ $requiredPort }}</strong> to function correctly.
|
||||
One or more of your domains are missing a port number.
|
||||
</x-callout>
|
||||
|
||||
<div class="flex flex-wrap gap-2 justify-between mt-4">
|
||||
<x-forms.button @click="modalOpen = false; $wire.call('cancelRemovePort')"
|
||||
class="w-auto dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
|
||||
Cancel - Keep Port
|
||||
</x-forms.button>
|
||||
<x-forms.button wire:click="confirmRemovePort" @click="modalOpen = false" class="w-auto"
|
||||
isError>
|
||||
I understand, remove port anyway
|
||||
</x-forms.button>
|
||||
<x-callout type="danger" title="What will happen if you continue?" class="mb-4">
|
||||
<ul class="mt-2 ml-4 list-disc">
|
||||
<li>The service may become unreachable</li>
|
||||
<li>The proxy may not be able to route traffic correctly</li>
|
||||
<li>Environment variables may not be generated properly</li>
|
||||
<li>The service may fail to start or function</li>
|
||||
</ul>
|
||||
</x-callout>
|
||||
|
||||
<div class="flex flex-wrap gap-2 justify-between mt-4">
|
||||
<x-forms.button @click="modalOpen = false; $wire.call('cancelRemovePort')"
|
||||
class="w-auto dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
|
||||
Cancel - Keep Port
|
||||
</x-forms.button>
|
||||
<x-forms.button wire:click="confirmRemovePort" @click="modalOpen = false" class="w-auto"
|
||||
isError>
|
||||
I understand, remove port anyway
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
@elseif ($resourceType === 'database')
|
||||
<x-slot:title>
|
||||
@@ -178,6 +184,17 @@
|
||||
</x-slot>
|
||||
@if ($currentRoute === 'project.service.database.import')
|
||||
<livewire:project.database.import :resource="$serviceDatabase" :key="'import-' . $serviceDatabase->uuid" />
|
||||
@elseif ($currentRoute === 'project.service.index.advanced')
|
||||
<h2>Advanced</h2>
|
||||
<div class="w-full sm:w-96 flex flex-col gap-1 pt-4">
|
||||
<x-forms.checkbox canGate="update" :canResource="$serviceDatabase" instantSave="instantSaveExclude"
|
||||
label="Exclude from service status"
|
||||
helper="If you do not need to monitor this resource, enable. Useful if this service is optional."
|
||||
id="excludeFromStatus"></x-forms.checkbox>
|
||||
<x-forms.checkbox canGate="update" :canResource="$serviceDatabase"
|
||||
helper="Drain logs to your configured log drain endpoint in your Server settings."
|
||||
instantSave="instantSaveLogDrain" id="isLogDrainEnabled" label="Drain Logs" />
|
||||
</div>
|
||||
@else
|
||||
<form wire:submit='submitDatabase'>
|
||||
<div class="flex items-center gap-2 pb-4">
|
||||
@@ -233,7 +250,7 @@
|
||||
<x-forms.checkbox canGate="update" :canResource="$serviceDatabase" instantSave id="isPublic"
|
||||
label="Make it publicly available" />
|
||||
</div>
|
||||
<x-forms.input canGate="update" :canResource="$serviceDatabase" placeholder="5432"
|
||||
<x-forms.input type="number" canGate="update" :canResource="$serviceDatabase" placeholder="5432"
|
||||
disabled="{{ $serviceDatabase->is_public }}" id="publicPort" label="Public Port" />
|
||||
@if ($db_url_public)
|
||||
<x-forms.input label="Database IP:PORT (public)"
|
||||
@@ -242,16 +259,6 @@
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="pt-2">Advanced</h3>
|
||||
<div class="w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$serviceDatabase" instantSave="instantSaveExclude"
|
||||
label="Exclude from service status"
|
||||
helper="If you do not need to monitor this resource, enable. Useful if this service is optional."
|
||||
id="excludeFromStatus"></x-forms.checkbox>
|
||||
<x-forms.checkbox canGate="update" :canResource="$serviceDatabase"
|
||||
helper="Drain logs to your configured log drain endpoint in your Server settings."
|
||||
instantSave="instantSaveLogDrain" id="isLogDrainEnabled" label="Drain Logs" />
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<div @class([
|
||||
'border-l border-dashed border-red-500' => str($resource->status)->contains(['exited']),
|
||||
'border-l border-dashed border-success' => str($resource->status)->contains(['running']),
|
||||
'border-l border-dashed border-warning' => str($resource->status)->contains(['starting', 'restarting']),
|
||||
'flex gap-2 box-without-bg-without-border dark:bg-coolgray-100 bg-white dark:hover:text-neutral-300 group',
|
||||
])>
|
||||
<div class="flex flex-row w-full">
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="pb-2">
|
||||
@if ($resource->human_name)
|
||||
{{ Str::headline($resource->human_name) }}
|
||||
@else
|
||||
{{ Str::headline($resource->name) }}
|
||||
@endif
|
||||
<span class="text-xs">({{ $resource->image }})</span>
|
||||
</div>
|
||||
@if ($resource->configuration_required)
|
||||
<span class="text-xs text-error">(configuration required)</span>
|
||||
@endif
|
||||
@if ($resource->description)
|
||||
<span class="text-xs">{{ Str::limit($resource->description, 60) }}</span>
|
||||
@endif
|
||||
@if ($isApplication && $resource->fqdn)
|
||||
<span class="flex gap-1 text-xs">{{ Str::limit($resource->fqdn, 60) }}
|
||||
@can('update', $service)
|
||||
<x-modal-input title="Edit Domains" :closeOutside="false">
|
||||
<x-slot:content>
|
||||
<span class="cursor-pointer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-4 h-4 dark:text-warning text-coollabs"
|
||||
viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2">
|
||||
<path d="m12 15l8.385-8.415a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zm4-10l3 3" />
|
||||
<path d="M9 7.07A7 7 0 0 0 10 21a7 7 0 0 0 6.929-6" />
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
</x-slot:content>
|
||||
<livewire:project.service.edit-domain applicationId="{{ $resource->id }}"
|
||||
wire:key="edit-domain-{{ $resource->id }}" />
|
||||
</x-modal-input>
|
||||
@endcan
|
||||
</span>
|
||||
@endif
|
||||
<div @class(['pt-2' => $isApplication, 'text-xs'])>{{ formatContainerStatus($resource->status) }}</div>
|
||||
</div>
|
||||
<div class="flex items-center px-4">
|
||||
@if ($isDatabase && ($resource->isBackupSolutionAvailable() || $resource->is_migrated))
|
||||
<a class="mx-4 text-xs font-bold hover:underline" {{ wireNavigate() }}
|
||||
href="{{ route('project.service.database.backups', [...$parameters, 'stack_service_uuid' => $resource->uuid]) }}">
|
||||
Backups
|
||||
</a>
|
||||
@endif
|
||||
<a class="mx-4 text-xs font-bold hover:underline" {{ wireNavigate() }}
|
||||
href="{{ route('project.service.index', [...$parameters, 'stack_service_uuid' => $resource->uuid]) }}">
|
||||
Settings
|
||||
</a>
|
||||
@if (str($resource->status)->contains('running'))
|
||||
@can('update', $service)
|
||||
<x-modal-confirmation :title="$isApplication ? 'Confirm Service Application Restart?' : 'Confirm Service Database Restart?'"
|
||||
buttonTitle="Restart" submitAction="restart" :actions="$isApplication
|
||||
? [
|
||||
'The selected service application will be unavailable during the restart.',
|
||||
'If the service application is currently in use data could be lost.',
|
||||
]
|
||||
: [
|
||||
'This service database will be unavailable during the restart.',
|
||||
'If the service database is currently in use data could be lost.',
|
||||
]"
|
||||
:confirmWithText="false" :confirmWithPassword="false"
|
||||
:step2ButtonText="$isApplication ? 'Restart Service Container' : 'Restart Database'" />
|
||||
@endcan
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -12,6 +12,9 @@
|
||||
<livewire:project.service.edit-compose serviceId="{{ $service->id }}" />
|
||||
</x-modal-input>
|
||||
@endcan
|
||||
<x-modal-input title="Resource Details" buttonTitle="Details">
|
||||
<livewire:project.shared.resource-details :resource="$service" />
|
||||
</x-modal-input>
|
||||
</div>
|
||||
<div>Configuration</div>
|
||||
</div>
|
||||
@@ -20,7 +23,10 @@
|
||||
placeholder="My super WordPress site" />
|
||||
<x-forms.input canGate="update" :canResource="$service" id="description" label="Description" />
|
||||
</div>
|
||||
<div class="w-96">
|
||||
<div>
|
||||
<h3>Network</h3>
|
||||
</div>
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$service" instantSave id="connectToDockerNetwork"
|
||||
label="Connect To Predefined Network"
|
||||
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>." />
|
||||
@@ -46,4 +52,4 @@
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</form>
|
||||
</form>
|
||||
@@ -1,22 +1,77 @@
|
||||
<div>
|
||||
@if ($isConfigurationChanged && !is_null($resource->config_hash) && !$resource->isExited())
|
||||
<x-popup-small>
|
||||
<x-slot:title>
|
||||
The latest configuration has not been applied
|
||||
</x-slot:title>
|
||||
<x-slot:icon>
|
||||
<svg class="hidden w-10 h-10 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" />
|
||||
</svg>
|
||||
</x-slot:icon>
|
||||
<x-slot:description>
|
||||
<span>Please redeploy to apply the new configuration.</span>
|
||||
</x-slot:description>
|
||||
<x-slot:button-text @click="disableSponsorship()">
|
||||
Disable This Popup
|
||||
</x-slot:button-text>
|
||||
</x-popup-small>
|
||||
<div x-data="{ configurationDiffModalOpen: false, expandedRows: {} }">
|
||||
<x-popup-small>
|
||||
<x-slot:title>
|
||||
The latest configuration has not been applied
|
||||
</x-slot:title>
|
||||
<x-slot:icon>
|
||||
<svg class="hidden w-10 h-10 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" />
|
||||
</svg>
|
||||
</x-slot:icon>
|
||||
<x-slot:description>
|
||||
<span>
|
||||
@if (data_get($configurationDiff, 'count'))
|
||||
{{ data_get($configurationDiff, 'count') }} unapplied configuration
|
||||
{{ data_get($configurationDiff, 'count') === 1 ? 'change' : 'changes' }} detected.
|
||||
@if (data_get($configurationDiff, 'requires_build'))
|
||||
A rebuild is required.
|
||||
@else
|
||||
Please redeploy to apply the new configuration.
|
||||
@endif
|
||||
<button type="button" class="ml-1 font-semibold underline text-coollabs dark:text-warning"
|
||||
x-on:click="$wire.refreshConfigurationChanges().then(() => configurationDiffModalOpen = true)"
|
||||
wire:loading.attr="disabled" wire:target="refreshConfigurationChanges">
|
||||
View changes
|
||||
</button>
|
||||
@else
|
||||
Please redeploy to apply the new configuration.
|
||||
@endif
|
||||
</span>
|
||||
</x-slot:description>
|
||||
</x-popup-small>
|
||||
|
||||
@if (data_get($configurationDiff, 'count'))
|
||||
<template x-teleport="body">
|
||||
<div x-show="configurationDiffModalOpen" x-cloak
|
||||
class="fixed inset-0 z-99 flex h-screen w-screen items-center justify-center p-4"
|
||||
@keydown.escape.window="configurationDiffModalOpen = false">
|
||||
<div x-show="configurationDiffModalOpen" x-transition.opacity
|
||||
class="absolute inset-0 h-full w-full bg-black/20 backdrop-blur-xs"
|
||||
@click="configurationDiffModalOpen = false"></div>
|
||||
<div x-show="configurationDiffModalOpen" x-trap.inert.noscroll="configurationDiffModalOpen"
|
||||
x-transition:enter="ease-out duration-100"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave="ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||
class="relative flex max-h-[85vh] w-full flex-col rounded-sm border border-neutral-200 bg-white shadow-lg dark:border-coolgray-300 dark:bg-base lg:max-w-4xl">
|
||||
<div class="flex shrink-0 items-center justify-between border-b border-neutral-200 px-6 py-5 dark:border-coolgray-300">
|
||||
<div>
|
||||
<h3 class="text-2xl font-bold text-black dark:text-white">Configuration changes</h3>
|
||||
<p class="mt-1 text-sm text-neutral-600 dark:text-neutral-400">
|
||||
These changes are not applied to the latest deployment yet.
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" @click="configurationDiffModalOpen = false"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-full dark:text-white hover:bg-neutral-100 dark:hover:bg-coolgray-300 outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-coollabs dark:focus-visible:ring-warning focus-visible:ring-offset-2 dark:focus-visible:ring-offset-base">
|
||||
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="overflow-y-auto p-6">
|
||||
<x-deployment.configuration-diff :diff="$configurationDiff" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -1,16 +1,23 @@
|
||||
<form class="flex flex-col w-full gap-2 rounded-sm" wire:submit='submit'>
|
||||
<form class="flex flex-col w-full gap-2 rounded-sm" wire:submit='submit'
|
||||
x-data="{ isMultiline: $wire.entangle('is_multiline') }">
|
||||
<x-forms.input placeholder="NODE_ENV" id="key" label="Name" required />
|
||||
@if ($is_multiline)
|
||||
<x-forms.textarea id="value" label="Value" required />
|
||||
@else
|
||||
<x-forms.env-var-input placeholder="production" id="value" label="Value" required
|
||||
:availableVars="$shared ? [] : $this->availableSharedVariables"
|
||||
:projectUuid="data_get($parameters, 'project_uuid')"
|
||||
:environmentUuid="data_get($parameters, 'environment_uuid')" />
|
||||
@endif
|
||||
<template x-if="isMultiline">
|
||||
<div wire:key="env-value-textarea">
|
||||
<x-forms.textarea id="value" label="Value" required class="font-sans" spellcheck />
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="!isMultiline">
|
||||
<div wire:key="env-value-input">
|
||||
<x-forms.env-var-input placeholder="production" id="value" label="Value" required
|
||||
:availableVars="$shared ? [] : $this->availableSharedVariables"
|
||||
:projectUuid="data_get($parameters, 'project_uuid')"
|
||||
:environmentUuid="data_get($parameters, 'environment_uuid')"
|
||||
:serverUuid="data_get($parameters, 'server_uuid')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@if (!$shared && !$is_multiline)
|
||||
<div class="text-xs text-neutral-500 dark:text-neutral-400 -mt-1">
|
||||
@if (!$shared)
|
||||
<div x-cloak x-show="!isMultiline" wire:key="env-value-tip" class="text-xs text-neutral-500 dark:text-neutral-400 -mt-1">
|
||||
Tip: Type <span class="font-mono dark:text-warning text-coollabs">{{</span> to reference a shared environment
|
||||
variable
|
||||
</div>
|
||||
@@ -33,8 +40,8 @@
|
||||
label="Is Literal?" />
|
||||
@endif
|
||||
|
||||
<x-forms.checkbox id="is_multiline" label="Is Multiline?" />
|
||||
<x-forms.checkbox id="is_multiline" x-model="isMultiline" label="Is Multiline?" />
|
||||
<x-forms.button type="submit" @click="slideOverOpen=false">
|
||||
Save
|
||||
</x-forms.button>
|
||||
</form>
|
||||
</form>
|
||||
@@ -84,24 +84,24 @@
|
||||
Inline comments with space before # (e.g., <code class="font-mono">KEY=value #comment</code>) are stripped.
|
||||
</x-callout>
|
||||
|
||||
<x-forms.textarea rows="10" class="whitespace-pre-wrap" id="variables" wire:model="variables"
|
||||
<x-forms.textarea rows="10" class="whitespace-pre-wrap font-sans" 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"
|
||||
<x-forms.textarea rows="10" class="whitespace-pre-wrap font-sans" 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>
|
||||
@else
|
||||
<x-forms.textarea rows="10" class="whitespace-pre-wrap" id="variables" wire:model="variables"
|
||||
<x-forms.textarea rows="10" class="whitespace-pre-wrap font-sans" id="variables" wire:model="variables"
|
||||
label="Production Environment Variables" disabled></x-forms.textarea>
|
||||
|
||||
@if ($showPreview)
|
||||
<x-forms.textarea rows="10" class="whitespace-pre-wrap" label="Preview Deployments Environment Variables"
|
||||
<x-forms.textarea rows="10" class="whitespace-pre-wrap font-sans" label="Preview Deployments Environment Variables"
|
||||
id="variablesPreview" wire:model="variablesPreview" disabled></x-forms.textarea>
|
||||
@endif
|
||||
@endcan
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,6 +43,12 @@
|
||||
@endif
|
||||
@else
|
||||
@if ($is_shared)
|
||||
<x-forms.checkbox instantSave id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
<x-forms.checkbox instantSave id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@@ -52,7 +58,7 @@
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
@endif
|
||||
@else
|
||||
@if (!$env->is_nixpacks)
|
||||
@if (!$env->is_buildpack_control)
|
||||
<x-forms.checkbox instantSave id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
@@ -61,7 +67,7 @@
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
@if (!$isMagicVariable)
|
||||
@if (!$env->is_nixpacks)
|
||||
@if (!$env->is_buildpack_control)
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
@if ($is_multiline === false)
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
@@ -89,6 +95,12 @@
|
||||
@endif
|
||||
@else
|
||||
@if ($is_shared)
|
||||
<x-forms.checkbox disabled id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
<x-forms.checkbox disabled id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
<x-forms.checkbox disabled id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@@ -129,7 +141,14 @@
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||
<x-forms.input disabled id="key" />
|
||||
<x-forms.input disabled type="password" id="value" />
|
||||
<x-forms.env-var-input
|
||||
disabled
|
||||
type="password"
|
||||
id="value"
|
||||
:availableVars="$isSharedVariable ? [] : $this->availableSharedVariables"
|
||||
:projectUuid="data_get($parameters, 'project_uuid')"
|
||||
:environmentUuid="data_get($parameters, 'environment_uuid')"
|
||||
:serverUuid="data_get($parameters, 'server_uuid')" />
|
||||
@if ($is_shared)
|
||||
<x-forms.input disabled type="password" id="real_value" />
|
||||
@endif
|
||||
@@ -143,10 +162,21 @@
|
||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||
@if ($is_multiline)
|
||||
<x-forms.input :required="$is_redis_credential" isMultiline="{{ $is_multiline }}" id="key" />
|
||||
<x-forms.textarea :required="$is_redis_credential" type="password" id="value" />
|
||||
<div class="flex-1" wire:key="env-show-value-textarea-{{ $env->id }}">
|
||||
<x-forms.textarea :required="$is_redis_credential" type="password" id="value" />
|
||||
</div>
|
||||
@else
|
||||
<x-forms.input :disabled="$is_redis_credential" :required="$is_redis_credential" id="key" />
|
||||
<x-forms.input :required="$is_redis_credential" type="password" id="value" />
|
||||
<div class="w-full" wire:key="env-show-value-input-{{ $env->id }}">
|
||||
<x-forms.env-var-input
|
||||
:required="$is_redis_credential"
|
||||
type="password"
|
||||
id="value"
|
||||
:availableVars="$isSharedVariable ? [] : $this->availableSharedVariables"
|
||||
:projectUuid="data_get($parameters, 'project_uuid')"
|
||||
:environmentUuid="data_get($parameters, 'environment_uuid')"
|
||||
:serverUuid="data_get($parameters, 'server_uuid')" />
|
||||
</div>
|
||||
@endif
|
||||
@if ($is_shared)
|
||||
<x-forms.input :disabled="$is_redis_credential" :required="$is_redis_credential" disabled
|
||||
@@ -161,7 +191,14 @@
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||
<x-forms.input disabled id="key" />
|
||||
<x-forms.input disabled type="password" id="value" />
|
||||
<x-forms.env-var-input
|
||||
disabled
|
||||
type="password"
|
||||
id="value"
|
||||
:availableVars="$isSharedVariable ? [] : $this->availableSharedVariables"
|
||||
:projectUuid="data_get($parameters, 'project_uuid')"
|
||||
:environmentUuid="data_get($parameters, 'environment_uuid')"
|
||||
:serverUuid="data_get($parameters, 'server_uuid')" />
|
||||
@if ($is_shared)
|
||||
<x-forms.input disabled type="password" id="real_value" />
|
||||
@endif
|
||||
@@ -184,6 +221,12 @@
|
||||
@endif
|
||||
@else
|
||||
@if ($is_shared)
|
||||
<x-forms.checkbox instantSave id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
<x-forms.checkbox instantSave id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@@ -193,7 +236,7 @@
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
@endif
|
||||
@else
|
||||
@if (!$env->is_nixpacks)
|
||||
@if (!$env->is_buildpack_control)
|
||||
<x-forms.checkbox instantSave id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
@@ -202,7 +245,7 @@
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
@if (!$isMagicVariable)
|
||||
@if (!$env->is_nixpacks)
|
||||
@if (!$env->is_buildpack_control)
|
||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||
@if ($is_multiline === false)
|
||||
<x-forms.checkbox instantSave id="is_literal"
|
||||
@@ -258,6 +301,12 @@
|
||||
@endif
|
||||
@else
|
||||
@if ($is_shared)
|
||||
<x-forms.checkbox disabled id="is_buildtime"
|
||||
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||
label="Available at Buildtime" />
|
||||
<x-forms.checkbox disabled id="is_runtime"
|
||||
helper="Make this variable available in the running container at runtime."
|
||||
label="Available at Runtime" />
|
||||
<x-forms.checkbox disabled id="is_literal"
|
||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||
label="Is Literal?" />
|
||||
@@ -291,4 +340,4 @@
|
||||
@endif
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,6 +28,38 @@
|
||||
}
|
||||
},
|
||||
isScrolling: false,
|
||||
lastTouchY: 0,
|
||||
disableFollow() {
|
||||
if (!this.alwaysScroll) return;
|
||||
this.alwaysScroll = false;
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.rafId = null;
|
||||
}
|
||||
},
|
||||
handleWheel(event) {
|
||||
if (this.alwaysScroll && event.deltaY < 0) {
|
||||
this.disableFollow();
|
||||
}
|
||||
},
|
||||
handleTouchStart(event) {
|
||||
this.lastTouchY = event.touches[0].clientY;
|
||||
},
|
||||
handleTouchMove(event) {
|
||||
if (!this.alwaysScroll) return;
|
||||
const currentY = event.touches[0].clientY;
|
||||
if (currentY > this.lastTouchY) {
|
||||
this.disableFollow();
|
||||
}
|
||||
this.lastTouchY = currentY;
|
||||
},
|
||||
handleKeyScroll(event) {
|
||||
if (!this.alwaysScroll) return;
|
||||
const upKeys = ['ArrowUp', 'PageUp', 'Home'];
|
||||
if (upKeys.includes(event.key)) {
|
||||
this.disableFollow();
|
||||
}
|
||||
},
|
||||
scrollToBottom() {
|
||||
const logsContainer = document.getElementById('logsContainer');
|
||||
if (logsContainer) {
|
||||
@@ -57,17 +89,14 @@
|
||||
}
|
||||
},
|
||||
handleScroll(event) {
|
||||
if (!this.alwaysScroll || this.isScrolling) return;
|
||||
if (this.isScrolling) return;
|
||||
clearTimeout(this.scrollDebounce);
|
||||
this.scrollDebounce = setTimeout(() => {
|
||||
const el = event.target;
|
||||
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
||||
if (distanceFromBottom > 100) {
|
||||
this.alwaysScroll = false;
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.rafId = null;
|
||||
}
|
||||
if (!this.alwaysScroll && distanceFromBottom <= 10) {
|
||||
this.alwaysScroll = true;
|
||||
this.scheduleScroll();
|
||||
}
|
||||
}, 150);
|
||||
},
|
||||
@@ -245,10 +274,13 @@
|
||||
<div>({{ $pull_request }})</div>
|
||||
@endif
|
||||
@if ($streamLogs)
|
||||
<x-loading wire:poll.2000ms='getLogs(true)' />
|
||||
<x-loading />
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@if ($streamLogs)
|
||||
<div class="sr-only" wire:poll.2000ms="getLogs(true)" aria-hidden="true"></div>
|
||||
@endif
|
||||
<div x-show="expanded" {{ $collapsible ? 'x-collapse' : '' }}
|
||||
:class="fullscreen ? 'fullscreen flex flex-col !overflow-visible' : 'relative w-full {{ $collapsible ? 'py-4' : '' }} mx-auto'"
|
||||
:style="fullscreen ? 'max-height: none !important; height: 100% !important;' : ''">
|
||||
@@ -473,14 +505,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="logsContainer" @scroll="handleScroll"
|
||||
<div id="logsContainer" @scroll="handleScroll" @wheel="handleWheel"
|
||||
@touchstart="handleTouchStart" @touchmove="handleTouchMove" @keydown="handleKeyScroll" tabindex="0"
|
||||
class="flex overflow-y-auto overflow-x-hidden flex-col px-4 py-2 w-full min-w-0 scrollbar"
|
||||
:class="fullscreen ? 'flex-1' : 'max-h-[40rem]'">
|
||||
@if ($outputs)
|
||||
@php
|
||||
$displayLines = collect(explode("\n", $outputs))->filter(fn($line) => trim($line) !== '');
|
||||
@endphp
|
||||
<div id="logs" class="font-mono max-w-full cursor-default">
|
||||
<div id="logs" class="font-logs max-w-full cursor-default">
|
||||
<div x-show="searchQuery.trim() && matchCount === 0"
|
||||
class="text-gray-500 dark:text-gray-400 py-2">
|
||||
No matches found.
|
||||
@@ -490,20 +523,19 @@
|
||||
// Parse timestamp from log line (ISO 8601 format: 2025-12-04T11:48:39.136764033Z)
|
||||
$timestamp = '';
|
||||
$logContent = $line;
|
||||
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}:\d{2}:\d{2})(?:\.(\d+))?Z?\s(.*)$/', $line, $matches)) {
|
||||
$year = $matches[1];
|
||||
$month = $matches[2];
|
||||
$day = $matches[3];
|
||||
$time = $matches[4];
|
||||
$microseconds = isset($matches[5]) ? substr($matches[5], 0, 6) : '000000';
|
||||
$logContent = $matches[6];
|
||||
if (preg_match('/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d+))?Z?\s(.*)$/', $line, $matches)) {
|
||||
$microseconds = isset($matches[2]) ? substr($matches[2], 0, 6) : '000000';
|
||||
$logContent = $matches[3];
|
||||
|
||||
// Convert month number to abbreviated name
|
||||
$monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
$monthName = $monthNames[(int)$month - 1] ?? $month;
|
||||
|
||||
// Format for display: 2025-Dec-04 09:44:58
|
||||
$timestamp = "{$year}-{$monthName}-{$day} {$time}";
|
||||
// Convert UTC Docker timestamp to server timezone for display
|
||||
$carbonTs = \Carbon\Carbon::parse($matches[1], 'UTC');
|
||||
$serverTz = getServerTimezone($server);
|
||||
try {
|
||||
$carbonTs->setTimezone($serverTz);
|
||||
} catch (\Exception) {
|
||||
// keep UTC
|
||||
}
|
||||
$timestamp = $carbonTs->format('Y-M-d H:i:s');
|
||||
// Include microseconds in key for uniqueness
|
||||
$lineKey = "{$timestamp}.{$microseconds}";
|
||||
}
|
||||
@@ -518,7 +550,7 @@
|
||||
</div>
|
||||
@else
|
||||
<pre id="logs"
|
||||
class="font-mono whitespace-pre-wrap break-all max-w-full text-neutral-400">No logs yet.</pre>
|
||||
class="font-logs whitespace-pre-wrap break-all max-w-full text-neutral-400">No logs yet.</pre>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<form wire:submit='submit' class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Healthchecks</h2>
|
||||
<h2>Healthcheck</h2>
|
||||
<x-forms.button canGate="update" :canResource="$resource" type="submit">Save</x-forms.button>
|
||||
@if (!$healthCheckEnabled)
|
||||
<x-modal-confirmation title="Confirm Healthcheck Enable?" buttonTitle="Enable Healthcheck"
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<div class="w-full max-h-[70vh] overflow-y-auto pr-1 -mt-4">
|
||||
<div class="pb-4 text-sm dark:text-neutral-400">Identifiers for this resource. Read-only</div>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<div>
|
||||
<h3>Resource</h3>
|
||||
<div class="pt-2 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<x-forms.copy-button label="Name" :text="$resource->name ?? ''" />
|
||||
<x-forms.copy-button label="UUID" :text="$resource->uuid ?? ''" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($environment_uuid)
|
||||
<div>
|
||||
<h3>Environment</h3>
|
||||
<div class="pt-2 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<x-forms.copy-button label="Name" :text="$environment_name ?? ''" />
|
||||
<x-forms.copy-button label="UUID" :text="$environment_uuid" />
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($project_uuid)
|
||||
<div>
|
||||
<h3>Project</h3>
|
||||
<div class="pt-2 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<x-forms.copy-button label="Name" :text="$project_name ?? ''" />
|
||||
<x-forms.copy-button label="UUID" :text="$project_uuid" />
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($server_uuid)
|
||||
<div>
|
||||
<h3>Server</h3>
|
||||
<div class="pt-2 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<x-forms.copy-button label="Name" :text="$server_name ?? ''" />
|
||||
<x-forms.copy-button label="UUID" :text="$server_uuid" />
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (! empty($stack_applications) || ! empty($stack_databases))
|
||||
<div>
|
||||
<h3>Stack Sub-Resources</h3>
|
||||
<div class="pt-2 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
@foreach ($stack_applications as $item)
|
||||
<x-forms.copy-button :label="'Application — ' . $item['name']" :text="$item['uuid']" />
|
||||
@endforeach
|
||||
@foreach ($stack_databases as $item)
|
||||
<x-forms.copy-button :label="'Database — ' . $item['name']" :text="$item['uuid']" />
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,24 +18,20 @@
|
||||
label="CPU Weight" id="limitsCpuShares" />
|
||||
</div>
|
||||
<h3 class="pt-4">Limit Memory</h3>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$resource"
|
||||
helper="Examples: 69b (byte) or 420k (kilobyte) or 1337m (megabyte) or 1g (gigabyte).<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_reservation'>here</a>."
|
||||
label="Soft Memory Limit" id="limitsMemoryReservation" />
|
||||
<x-forms.input canGate="update" :canResource="$resource"
|
||||
helper="0-100.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_swappiness'>here</a>."
|
||||
type="number" min="0" max="100" label="Swappiness"
|
||||
id="limitsMemorySwappiness" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$resource"
|
||||
helper="Examples: 69b (byte) or 420k (kilobyte) or 1337m (megabyte) or 1g (gigabyte).<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_limit'>here</a>."
|
||||
label="Maximum Memory Limit" id="limitsMemory" />
|
||||
<x-forms.input canGate="update" :canResource="$resource"
|
||||
helper="Examples:69b (byte) or 420k (kilobyte) or 1337m (megabyte) or 1g (gigabyte).<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#memswap_limit'>here</a>."
|
||||
label="Maximum Swap Limit" id="limitsMemorySwap" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$resource"
|
||||
helper="<span class='text-helper'>Examples</span><br>• 69b (byte)<br>• 420k (kilobyte)<br>• 1337m (megabyte)<br>• 1g (gigabyte)<br><br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_reservation'>here</a>."
|
||||
label="Soft Memory Limit" id="limitsMemoryReservation" />
|
||||
<x-forms.input canGate="update" :canResource="$resource"
|
||||
helper="Value between 0-100.<br><br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_swappiness'>here</a>."
|
||||
type="number" min="0" max="100" label="Swappiness"
|
||||
id="limitsMemorySwappiness" />
|
||||
<x-forms.input canGate="update" :canResource="$resource"
|
||||
helper="<span class='text-helper'>Examples</span><br>• 69b (byte)<br>• 420k (kilobyte)<br>• 1337m (megabyte)<br>• 1g (gigabyte)<br><br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_limit'>here</a>."
|
||||
label="Maximum Memory Limit" id="limitsMemory" />
|
||||
<x-forms.input canGate="update" :canResource="$resource"
|
||||
helper="<span class='text-helper'>Examples</span><br>• 69b (byte)<br>• 420k (kilobyte)<br>• 1337m (megabyte)<br>• 1g (gigabyte)<br><br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#memswap_limit'>here</a>."
|
||||
label="Maximum Swap Limit" id="limitsMemorySwap" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -63,6 +63,9 @@
|
||||
}">
|
||||
<h3 class="pt-4">Clone Resource</h3>
|
||||
<div class="pb-2">Duplicate this resource to another server or network destination.</div>
|
||||
<x-callout type="info" title="Important" class="mb-4">
|
||||
Cloning only duplicates resource configuration (such as environment variables, build settings etc..). It does not include any resource data, such as databases or stored files.
|
||||
</x-callout>
|
||||
|
||||
@can('update', $resource)
|
||||
<div class="space-y-4 pb-8">
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
<div>
|
||||
<x-slot:title>
|
||||
{{ data_get_str($resource, 'name')->limit(10) }} > Scheduled Tasks | Coolify
|
||||
</x-slot>
|
||||
@if ($type === 'application')
|
||||
<h1>Scheduled Task</h1>
|
||||
<livewire:project.application.heading :application="$resource" />
|
||||
@elseif ($type === 'service')
|
||||
<livewire:project.service.heading :service="$resource" :parameters="$parameters" />
|
||||
@endif
|
||||
|
||||
<form wire:submit="submit" class="w-full">
|
||||
<div class="flex flex-col gap-2 pb-2">
|
||||
<div class="flex gap-2 items-end">
|
||||
<h2>Scheduled Task</h2>
|
||||
<h2>Task {{ $task->name }}</h2>
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@@ -21,6 +11,11 @@
|
||||
Execute Now
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@if (!$isEnabled)
|
||||
<x-forms.button wire:click="toggleEnabled" isHighlighted>Enable Task</x-forms.button>
|
||||
@else
|
||||
<x-forms.button wire:click="toggleEnabled">Disable Task</x-forms.button>
|
||||
@endif
|
||||
<x-modal-confirmation title="Confirm Scheduled Task Deletion?" isErrorButton buttonTitle="Delete"
|
||||
submitAction="delete({{ $task->id }})" :actions="['The selected scheduled task will be permanently deleted.']" confirmationText="{{ $task->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Scheduled Task Name below"
|
||||
@@ -28,13 +23,11 @@
|
||||
step2ButtonText="Permanently Delete" />
|
||||
|
||||
</div>
|
||||
<div class="w-48">
|
||||
<x-forms.checkbox instantSave id="isEnabled" label="Enabled" />
|
||||
</div>
|
||||
<h3 class="pt-4">Configuration</h3>
|
||||
<div class="flex gap-2 w-full">
|
||||
<x-forms.input placeholder="Name" id="name" label="Name" required />
|
||||
<x-forms.input placeholder="php artisan schedule:run" id="command" label="Command" required />
|
||||
<x-forms.input placeholder="0 0 * * * or daily" id="frequency" label="Frequency" required />
|
||||
<x-forms.input placeholder="0 0 * * * or daily" id="frequency" label="Frequency"
|
||||
helper="You can use every_minute, hourly, daily, weekly, monthly, yearly or a cron expression." required />
|
||||
<x-forms.input type="number" placeholder="300" id="timeout"
|
||||
helper="Maximum execution time in seconds (60-36000)." label="Timeout (seconds)" required />
|
||||
@if ($type === 'application')
|
||||
@@ -47,6 +40,7 @@
|
||||
id="container" label="Service name" />
|
||||
@endif
|
||||
</div>
|
||||
<x-forms.input placeholder="php artisan schedule:run" id="command" label="Command" required />
|
||||
</form>
|
||||
|
||||
<div class="pt-4">
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
@endif
|
||||
@if (!$isService)
|
||||
@can('update', $resource)
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox instantSave canGate="update" :canResource="$resource" label="Add suffix for PR deployments"
|
||||
id="isPreviewSuffixEnabled"
|
||||
helper="When enabled, a -pr-N suffix is added to this volume's name for preview deployments (e.g. myvolume becomes myvolume-pr-1). Disable this for volumes that should be shared between the main and preview deployments."></x-forms.checkbox>
|
||||
@@ -64,7 +64,7 @@
|
||||
</div>
|
||||
@endif
|
||||
@if (!$isService)
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox instantSave canGate="update" :canResource="$resource" label="Add suffix for PR deployments"
|
||||
id="isPreviewSuffixEnabled"
|
||||
helper="When enabled, a -pr-N suffix is added to this volume's name for preview deployments (e.g. myvolume becomes myvolume-pr-1). Disable this for volumes that should be shared between the main and preview deployments."></x-forms.checkbox>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<div id="terminal-container" x-data="terminalData()">
|
||||
@if ($isTerminalConnected)
|
||||
<div class="hidden" aria-hidden="true" wire:poll.keep-alive.30s="keepTerminalPageAlive"></div>
|
||||
@endif
|
||||
@if (!$hasShell)
|
||||
<div class="flex pt-4 items-center justify-center w-full py-4 mx-auto">
|
||||
<div class="p-4 w-full rounded-sm border dark:bg-coolgray-100 dark:border-coolgray-300">
|
||||
@@ -18,10 +21,37 @@
|
||||
</div>
|
||||
@endif
|
||||
<div x-ref="terminalWrapper"
|
||||
:class="fullscreen ? 'fullscreen !bg-black' : 'relative w-full h-full py-4 mx-auto max-h-[510px]'">
|
||||
:class="fullscreen ? 'fixed inset-0 z-[9999] m-0 h-[100dvh] w-screen max-w-none overflow-hidden rounded-none !bg-black p-0' : 'relative w-full h-full py-4 mx-auto max-h-[510px]'">
|
||||
<!-- Terminal container -->
|
||||
<div x-show="terminalActive" x-cloak class="mb-2 flex justify-start">
|
||||
<div class="inline-flex rounded-sm border px-2 py-1 text-xs font-medium"
|
||||
:class="terminalSessionTimerClass()" x-text="terminalSessionRemainingLabel()">
|
||||
</div>
|
||||
</div>
|
||||
<div id="terminal" wire:ignore
|
||||
:class="fullscreen ? 'px-2 py-1 h-full bg-black' : 'px-2 py-1 rounded-sm bg-black'" x-show="terminalActive">
|
||||
:class="fullscreen ? (mobileToolbarCollapsed ? 'h-[calc(100dvh-3.5rem)] mb-14 px-2 py-1 bg-black' : 'h-[calc(100dvh-6rem)] mb-[6rem] px-2 py-1 bg-black') : 'h-[510px] max-h-[calc(100dvh-10rem)] overflow-hidden px-2 py-1 rounded-sm bg-black'"
|
||||
x-show="terminalActive">
|
||||
</div>
|
||||
<div x-show="terminalActive" x-cloak
|
||||
:class="fullscreen ? 'absolute inset-x-0 bottom-0 z-[9999] px-2 pb-2' : 'relative mt-2'"
|
||||
class="sm:hidden" data-terminal-mobile-toolbar>
|
||||
<div class="mx-auto max-w-3xl rounded-lg border border-white/10 bg-black/90 p-1.5 text-white shadow-lg backdrop-blur">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="px-2 text-[11px] font-medium uppercase tracking-wide text-neutral-400">Terminal keys</span>
|
||||
<button type="button" class="rounded px-2 py-1 text-xs text-neutral-300 hover:bg-white/10 hover:text-white"
|
||||
x-on:click="mobileToolbarCollapsed = !mobileToolbarCollapsed; $nextTick(() => resizeTerminal())"
|
||||
x-text="mobileToolbarCollapsed ? 'Show' : 'Hide'"
|
||||
aria-label="Toggle mobile terminal toolbar"></button>
|
||||
</div>
|
||||
<div x-show="!mobileToolbarCollapsed" class="mt-1 grid grid-cols-6 gap-1">
|
||||
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalControl('arrowUp')" aria-label="Previous command">↑</button>
|
||||
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalControl('arrowDown')" aria-label="Next command">↓</button>
|
||||
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalControl('arrowLeft')" aria-label="Move cursor left">←</button>
|
||||
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalControl('arrowRight')" aria-label="Move cursor right">→</button>
|
||||
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalControl('tab')">Tab</button>
|
||||
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalControl('escape')">Esc</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button title="Minimize" x-show="fullscreen" class="fixed bg-black/40 top-4 right-6 text-white"
|
||||
x-on:click="makeFullscreen"><svg class="w-5 h-5 text-gray-500 hover:text-white bg-black/80"
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Webhooks</h2>
|
||||
<x-helper
|
||||
helper="For more details goto our <a class='underline dark:text-white' href='https://coolify.io/docs/api-reference/api/operations/deploy-by-tag-or-uuid' target='_blank'>docs</a>." />
|
||||
helper="For more details goto our <a class='underline dark:text-white' href='https://coolify.io/docs/api-reference/authorization' target='_blank'>docs</a>." />
|
||||
</div>
|
||||
<div>
|
||||
<x-forms.input readonly
|
||||
helper="See details in our <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/api-reference/api/operations/deploy-by-tag-or-uuid'>documentation</a>."
|
||||
helper="See details in our <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/api-reference/authorization'>documentation</a>."
|
||||
label="Deploy Webhook (auth required)" id="deploywebhook"></x-forms.input>
|
||||
</div>
|
||||
@if ($resource->type() === 'application')
|
||||
|
||||
@@ -14,13 +14,19 @@
|
||||
<h3>New Token</h3>
|
||||
@can('create', App\Models\PersonalAccessToken::class)
|
||||
<form class="flex flex-col gap-2" wire:submit='addNewToken'>
|
||||
<div class="flex gap-2 items-end w-96">
|
||||
<x-forms.input required id="description" label="Description" />
|
||||
<div class="flex gap-2 items-end w-lg">
|
||||
<x-forms.input class="w-64" required id="description" label="Description" />
|
||||
<x-forms.select id="expiresInDays" label="Expires in" wire:model="expiresInDays">
|
||||
@foreach ($expirationOptions as $days => $label)
|
||||
<option value="{{ $days }}">{{ $label }}</option>
|
||||
@endforeach
|
||||
<option value="">Never</option>
|
||||
</x-forms.select>
|
||||
<x-forms.button type="submit">Create</x-forms.button>
|
||||
</div>
|
||||
<div class="flex">
|
||||
Permissions
|
||||
<x-helper class="px-1" helper="These permissions will be granted to the token." /><span
|
||||
<span
|
||||
class="pr-1">:</span>
|
||||
<div class="flex gap-1 font-bold dark:text-white">
|
||||
@if ($permissions)
|
||||
@@ -31,7 +37,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>Token Permissions</h4>
|
||||
<div class="w-64">
|
||||
@if ($canUseRootPermissions)
|
||||
<x-forms.checkbox label="root" wire:model.live="permissions" domValue="root"
|
||||
@@ -71,38 +76,78 @@
|
||||
<div class="pb-4 font-bold dark:text-white"> {{ session('token') }}</div>
|
||||
@endif
|
||||
<h3 class="py-4">Issued Tokens</h3>
|
||||
<div class="grid gap-2 lg:grid-cols-1">
|
||||
@forelse ($tokens as $token)
|
||||
<div wire:key="token-{{ $token->id }}"
|
||||
class="flex flex-col gap-1 p-2 border dark:border-coolgray-200 hover:no-underline">
|
||||
<div>Description: {{ $token->name }}</div>
|
||||
<div>Last used: {{ $token->last_used_at ? $token->last_used_at->diffForHumans() : 'Never' }}</div>
|
||||
<div class="flex gap-1">
|
||||
@if ($token->abilities)
|
||||
Permissions:
|
||||
@foreach ($token->abilities as $ability)
|
||||
<div class="font-bold dark:text-white">{{ $ability }}</div>
|
||||
@endforeach
|
||||
@endif
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col">
|
||||
<div class="overflow-x-auto">
|
||||
<div class="inline-block min-w-full">
|
||||
<div class="overflow-hidden">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Description</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Permissions</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Last used</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Created</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Expires</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($tokens as $token)
|
||||
<tr wire:key="token-{{ $token->id }}">
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">{{ $token->name }}</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
@if ($token->abilities)
|
||||
<div class="flex gap-1">
|
||||
@foreach ($token->abilities as $ability)
|
||||
<div class="font-bold dark:text-white">{{ $ability }}</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ $token->last_used_at ? $token->last_used_at->diffForHumans() : 'Never' }}
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ $token->created_at->diffForHumans() }}
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
@if (! $token->expires_at)
|
||||
Never
|
||||
@elseif ($token->expires_at->isPast())
|
||||
<span class="font-bold dark:text-error">Expired
|
||||
{{ $token->expires_at->format('Y-m-d H:i:s') }}</span>
|
||||
@else
|
||||
{{ $token->expires_at->format('Y-m-d H:i:s') }}
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm font-medium whitespace-nowrap">
|
||||
@if (auth()->id() === $token->tokenable_id)
|
||||
<x-modal-confirmation title="Confirm API Token Revocation?" isErrorButton
|
||||
buttonTitle="Revoke token"
|
||||
submitAction="revoke({{ data_get($token, 'id') }})" :actions="[
|
||||
'This API Token will be revoked and permanently deleted.',
|
||||
'Any API call made with this token will fail.',
|
||||
]"
|
||||
confirmationText="{{ $token->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the API Token Description below"
|
||||
shortConfirmationLabel="API Token Description" :confirmWithPassword="false"
|
||||
step2ButtonText="Revoke API Token" />
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap" colspan="6">No API tokens found.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (auth()->id() === $token->tokenable_id)
|
||||
<x-modal-confirmation title="Confirm API Token Revocation?" isErrorButton buttonTitle="Revoke token"
|
||||
submitAction="revoke({{ data_get($token, 'id') }})" :actions="[
|
||||
'This API Token will be revoked and permanently deleted.',
|
||||
'Any API call made with this token will fail.',
|
||||
]"
|
||||
confirmationText="{{ $token->name }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the API Token Description below"
|
||||
shortConfirmationLabel="API Token Description" :confirmWithPassword="false"
|
||||
step2ButtonText="Revoke API Token" />
|
||||
@endif
|
||||
</div>
|
||||
@empty
|
||||
<div>
|
||||
<div>No API tokens found.</div>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -13,7 +13,7 @@
|
||||
<x-forms.input id="name" label="Name" required />
|
||||
<x-forms.input id="description" label="Description" />
|
||||
</div>
|
||||
<x-forms.textarea realtimeValidation id="value" rows="10"
|
||||
<x-forms.textarea realtimeValidation id="value" rows="10" monospace
|
||||
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" required />
|
||||
<x-forms.input id="publicKey" readonly label="Public Key" />
|
||||
<span class="pt-2 pb-4 font-bold dark:text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
required disabled />
|
||||
</div>
|
||||
<div x-cloak x-show="showPrivateKey">
|
||||
<x-forms.textarea canGate="update" :canResource="$private_key" rows="10" id="privateKeyValue" required />
|
||||
<x-forms.textarea canGate="update" :canResource="$private_key" rows="10" id="privateKeyValue" required monospace />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
id="serverDiskUsageCheckFrequency" label="Disk usage check frequency" required
|
||||
helper="Cron expression for disk usage check frequency.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every night at 11:00 PM." />
|
||||
<x-forms.input canGate="update" :canResource="$server" id="serverDiskUsageNotificationThreshold"
|
||||
type="number" min="1" max="99"
|
||||
label="Server disk usage notification threshold (%)" required
|
||||
helper="If the server disk usage exceeds this threshold, Coolify will send a notification to the team members." />
|
||||
</div>
|
||||
@@ -31,12 +32,15 @@
|
||||
<h3>Builds</h3>
|
||||
<div class="flex flex-wrap gap-2 sm:flex-nowrap pt-4">
|
||||
<x-forms.input canGate="update" :canResource="$server" id="concurrentBuilds"
|
||||
type="number" min="1"
|
||||
label="Number of concurrent builds" required
|
||||
helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
|
||||
<x-forms.input canGate="update" :canResource="$server" id="dynamicTimeout"
|
||||
type="number" min="1"
|
||||
label="Deployment timeout (seconds)" required
|
||||
helper="You can define the maximum duration for a deployment to run before timing it out." />
|
||||
<x-forms.input canGate="update" :canResource="$server" id="deploymentQueueLimit"
|
||||
type="number" min="1"
|
||||
label="Deployment queue limit" required
|
||||
helper="Maximum number of queued deployments allowed. New deployments will be rejected with a 429 status when the limit is reached." />
|
||||
</div>
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
<x-forms.button>{{ data_get($docker, 'network') }} </x-forms.button>
|
||||
</a>
|
||||
@endforeach
|
||||
@if ($server->standaloneDockers->isEmpty() && $server->swarmDockers->isEmpty())
|
||||
<div class="text-sm text-neutral-500">No destinations configured for this server yet.</div>
|
||||
@endif
|
||||
</div>
|
||||
@if ($networks->count() > 0)
|
||||
<div class="pt-2">
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
<svg class="h-5 w-5 shrink-0 text-gray-500 dark:text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<code class="flex-1 text-sm font-mono text-gray-700 dark:text-gray-300">{{ data_get($result, 'command') }}</code>
|
||||
<code class="flex-1 text-sm font-logs text-gray-700 dark:text-gray-300">{{ data_get($result, 'command') }}</code>
|
||||
</div>
|
||||
@php
|
||||
$output = data_get($result, 'output');
|
||||
@@ -108,7 +108,7 @@
|
||||
@endphp
|
||||
<div class="p-4">
|
||||
@if($hasOutput)
|
||||
<pre class="font-mono text-sm text-gray-600 dark:text-gray-300 whitespace-pre-wrap">{{ $output }}</pre>
|
||||
<pre class="font-logs text-sm text-gray-600 dark:text-gray-300 whitespace-pre-wrap">{{ $output }}</pre>
|
||||
@else
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 italic">
|
||||
No output returned - command completed successfully
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</x-callout>
|
||||
@endif
|
||||
<h3>Advanced</h3>
|
||||
<div class="pb-6 w-96">
|
||||
<div class="pb-6 w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$server"
|
||||
helper="If set, all resources will only have docker container labels for {{ str($server->proxyType())->title() }}.<br>For applications, labels needs to be regenerated manually. <br>Resources needs to be restarted."
|
||||
id="generateExactLabels"
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
<x-forms.checkbox canGate="update" :canResource="$server" wire:model.live="isSentinelEnabled"
|
||||
label="Enable Sentinel" />
|
||||
@if ($server->isSentinelEnabled())
|
||||
@@ -91,13 +91,14 @@
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
|
||||
<x-forms.input canGate="update" :canResource="$server"
|
||||
<x-forms.input canGate="update" :canResource="$server" type="number" min="1"
|
||||
id="sentinelMetricsRefreshRateSeconds" label="Metrics rate (seconds)" required
|
||||
helper="Interval used for gathering metrics. Lower values result in more disk space usage." />
|
||||
<x-forms.input canGate="update" :canResource="$server" id="sentinelMetricsHistoryDays"
|
||||
<x-forms.input canGate="update" :canResource="$server" type="number" min="1"
|
||||
id="sentinelMetricsHistoryDays"
|
||||
label="Metrics history (days)" required
|
||||
helper="Number of days to retain metrics data for." />
|
||||
<x-forms.input canGate="update" :canResource="$server"
|
||||
<x-forms.input canGate="update" :canResource="$server" type="number" min="10"
|
||||
id="sentinelPushIntervalSeconds" label="Push interval (seconds)" required
|
||||
helper="Interval at which metrics data is sent to the collector." />
|
||||
</div>
|
||||
|
||||
@@ -191,6 +191,12 @@
|
||||
label="Port" required :disabled="$isValidating" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-64">
|
||||
<x-forms.input canGate="update" :canResource="$server" type="number"
|
||||
id="connectionTimeout" label="SSH Connection Timeout (s)"
|
||||
helper="Seconds to wait for SSH connection before failing. Default: 10."
|
||||
min="1" max="300" required :disabled="$isValidating" />
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="flex items-center mb-1">
|
||||
<label for="serverTimezone">Server Timezone</label>
|
||||
@@ -274,7 +280,7 @@
|
||||
|
||||
<div class="w-full">
|
||||
@if (!$server->isLocalhost())
|
||||
<div class="w-96">
|
||||
<div class="w-full sm:w-96">
|
||||
@if ($isBuildServerLocked)
|
||||
<x-forms.checkbox disabled instantSave id="isBuildServer"
|
||||
helper="You can't use this server as a build server because it has defined resources."
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
<div class="w-full">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Swarm <span class="text-xs text-neutral-500">(experimental)</span></h2>
|
||||
<h2>Swarm</h2>
|
||||
<x-deprecated-badge />
|
||||
</div>
|
||||
<x-callout type="warning" title="Deprecated" class="my-4">
|
||||
{{ config('deprecations.swarm') }}
|
||||
</x-callout>
|
||||
<div class="pb-4">Read the docs <a class='underline dark:text-white'
|
||||
href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.
|
||||
</div>
|
||||
|
||||
@@ -103,142 +103,102 @@
|
||||
return new Date(b.published_at) - new Date(a.published_at);
|
||||
});
|
||||
}
|
||||
}" @click.outside="dropdownOpen = false">
|
||||
<!-- Custom Dropdown without arrow -->
|
||||
<div class="relative">
|
||||
<button @click="dropdownOpen = !dropdownOpen"
|
||||
class="relative p-2 dark:text-neutral-400 hover:dark:text-white transition-colors cursor-pointer"
|
||||
title="Preferences">
|
||||
<!-- Sliders Icon -->
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" title="Preferences">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
|
||||
}" @click.outside="dropdownOpen = false" class="{{ $trigger === 'changelog-sidebar' ? 'w-full' : '' }}">
|
||||
@if ($trigger === 'changelog-sidebar')
|
||||
<button wire:click="openWhatsNewModal" type="button" title="What's New" aria-label="What's New"
|
||||
class="relative text-left menu-item">
|
||||
<svg class="menu-item-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8"
|
||||
d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.091-3.091L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.091-3.091L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.091 3.091L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.091 3.091ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.456-2.456L14.25 6l1.035-.259a3.375 3.375 0 0 0 2.456-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z" />
|
||||
</svg>
|
||||
|
||||
<!-- Unread Count Badge -->
|
||||
<span class="text-left menu-item-label" :class="collapsed && 'lg:hidden'">What's New</span>
|
||||
@if ($unreadCount > 0)
|
||||
<span
|
||||
class="absolute -top-1 -right-1 bg-error text-white text-xs rounded-full w-4.5 h-4.5 flex items-center justify-center">
|
||||
class="absolute top-0 right-0 bg-error text-white text-[10px] rounded-full min-w-4 h-4 px-1 flex items-center justify-center"
|
||||
aria-label="{{ $unreadCount }} unread changelog {{ Str::plural('entry', $unreadCount) }}">
|
||||
{{ $unreadCount > 9 ? '9+' : $unreadCount }}
|
||||
</span>
|
||||
@endif
|
||||
</button>
|
||||
@else
|
||||
<!-- Custom Dropdown without arrow -->
|
||||
<div class="relative">
|
||||
<button @click="dropdownOpen = !dropdownOpen"
|
||||
class="relative p-2 dark:text-neutral-400 hover:dark:text-white transition-colors cursor-pointer"
|
||||
title="Preferences">
|
||||
<!-- Sliders Icon -->
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" title="Preferences">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
|
||||
</svg>
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
<div x-show="dropdownOpen" x-transition:enter="ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2" x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="ease-in duration-150" x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2" class="absolute right-0 top-full mt-1 z-50 w-48" x-cloak>
|
||||
<div
|
||||
class="p-1 bg-white border rounded-sm shadow-lg dark:bg-coolgray-200 dark:border-coolgray-300 border-neutral-300">
|
||||
<div class="flex flex-col gap-1">
|
||||
<!-- What's New Section -->
|
||||
@if ($unreadCount > 0)
|
||||
<button wire:click="openWhatsNewModal" @click="dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>What's New</span>
|
||||
</div>
|
||||
<span
|
||||
class="bg-error text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
|
||||
{{ $unreadCount > 9 ? '9+' : $unreadCount }}
|
||||
</span>
|
||||
<!-- Unread Count Badge -->
|
||||
@if ($unreadCount > 0)
|
||||
<span
|
||||
class="absolute -top-1 -right-1 bg-error text-white text-xs rounded-full w-4.5 h-4.5 flex items-center justify-center">
|
||||
{{ $unreadCount > 9 ? '9+' : $unreadCount }}
|
||||
</span>
|
||||
@endif
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
<div x-show="dropdownOpen" x-transition:enter="ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2" x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="ease-in duration-150" x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2" class="absolute right-0 top-full mt-1 z-50 w-48" x-cloak>
|
||||
<div
|
||||
class="p-1 bg-white border rounded-sm shadow-lg dark:bg-coolgray-200 dark:border-coolgray-300 border-neutral-300">
|
||||
<div class="flex flex-col gap-1">
|
||||
<!-- Width Section -->
|
||||
<div
|
||||
class="my-1 font-bold border-b dark:border-coolgray-500 border-neutral-300 dark:text-white text-md">
|
||||
Width</div>
|
||||
<button @click="switchWidth(); dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center gap-2" x-show="full === 'full'">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h7" />
|
||||
</svg>
|
||||
<span>Center</span>
|
||||
</button>
|
||||
@else
|
||||
<button wire:click="openWhatsNewModal" @click="dropdownOpen = false"
|
||||
<button @click="switchWidth(); dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center gap-2" x-show="full === 'center'">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
<span>Full</span>
|
||||
</button>
|
||||
|
||||
<!-- Zoom Section -->
|
||||
<div
|
||||
class="my-1 font-bold border-b dark:border-coolgray-500 border-neutral-300 dark:text-white text-md">
|
||||
Zoom</div>
|
||||
<button @click="setZoom(100); dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
<span>Changelog</span>
|
||||
<span>100%</span>
|
||||
</button>
|
||||
@endif
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="border-b dark:border-coolgray-500 border-neutral-300"></div>
|
||||
|
||||
<!-- Theme Section -->
|
||||
<div class="font-bold border-b dark:border-coolgray-500 border-neutral-300 dark:text-white pb-1">
|
||||
Appearance</div>
|
||||
<button @click="setTheme('dark'); dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||
</svg>
|
||||
<span>Dark</span>
|
||||
</button>
|
||||
<button @click="setTheme('light'); dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<span>Light</span>
|
||||
</button>
|
||||
<button @click="setTheme('system'); dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>System</span>
|
||||
</button>
|
||||
|
||||
<!-- Width Section -->
|
||||
<div
|
||||
class="my-1 font-bold border-b dark:border-coolgray-500 border-neutral-300 dark:text-white text-md">
|
||||
Width</div>
|
||||
<button @click="switchWidth(); dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center gap-2" x-show="full === 'full'">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h7" />
|
||||
</svg>
|
||||
<span>Center</span>
|
||||
</button>
|
||||
<button @click="switchWidth(); dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center gap-2" x-show="full === 'center'">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
<span>Full</span>
|
||||
</button>
|
||||
|
||||
<!-- Zoom Section -->
|
||||
<div
|
||||
class="my-1 font-bold border-b dark:border-coolgray-500 border-neutral-300 dark:text-white text-md">
|
||||
Zoom</div>
|
||||
<button @click="setZoom(100); dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
<span>100%</span>
|
||||
</button>
|
||||
<button @click="setZoom(90); dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 10h4v4h-4v-4z" />
|
||||
</svg>
|
||||
<span>90%</span>
|
||||
</button>
|
||||
<button @click="setZoom(90); dropdownOpen = false"
|
||||
class="px-1 dropdown-item-no-padding flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 10h4v4h-4v-4z" />
|
||||
</svg>
|
||||
<span>90%</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- What's New Modal -->
|
||||
@if ($showWhatsNewModal)
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center py-6 px-4"
|
||||
<div class="fixed inset-0 z-[60] flex items-center justify-center py-6 px-4"
|
||||
@keydown.escape.window="$wire.closeWhatsNewModal()">
|
||||
<!-- Background overlay -->
|
||||
<div class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs" wire:click="closeWhatsNewModal">
|
||||
@@ -262,8 +222,7 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
@if (isDev())
|
||||
<x-forms.button wire:click="manualFetchChangelog"
|
||||
class="bg-coolgray-200 hover:bg-coolgray-300">
|
||||
<x-forms.button wire:click="manualFetchChangelog">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<x-forms.input id="smtpUsername" label="SMTP Username" />
|
||||
<x-forms.input id="smtpPassword" type="password" label="SMTP Password"
|
||||
autocomplete="new-password" />
|
||||
<x-forms.input id="smtpTimeout" helper="Timeout value for sending emails." label="Timeout" />
|
||||
<x-forms.input id="smtpTimeout" type="number" helper="Timeout value for sending emails." label="Timeout" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -16,11 +16,31 @@
|
||||
<div class="pb-4">Advanced settings for your Coolify instance.</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="md:w-96">
|
||||
<x-forms.checkbox instantSave id="is_registration_enabled"
|
||||
helper="Allow users to self-register. If disabled, only administrators can create accounts."
|
||||
label="Registration Allowed" />
|
||||
</div>
|
||||
@if ($is_registration_enabled)
|
||||
<div class="md:w-96" wire:key="registration-enabled">
|
||||
<x-forms.checkbox instantSave id="is_registration_enabled"
|
||||
helper="Allow users to self-register. If disabled, only administrators can create accounts."
|
||||
label="Registration Allowed" />
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-center justify-between gap-2 md:w-96"
|
||||
wire:key="registration-disabled">
|
||||
<label class="flex items-center gap-2">
|
||||
Registration Allowed
|
||||
<x-helper
|
||||
helper="Allow users to self-register. If disabled, only administrators can create accounts.">
|
||||
</x-helper>
|
||||
</label>
|
||||
<x-modal-confirmation title="Enable Registration?" buttonTitle="Enable" isErrorButton
|
||||
submitAction="toggleRegistration" :actions="[
|
||||
'Enables registration for everyone',
|
||||
]"
|
||||
warningMessage="Enabling registration allows anyone to create an account on this instance. Make sure you understand the implications before proceeding."
|
||||
confirmationText="ENABLE REGISTRATION"
|
||||
confirmationLabel="Please type the confirmation text to enable registration."
|
||||
shortConfirmationLabel="Confirmation text" />
|
||||
</div>
|
||||
@endif
|
||||
<div class="md:w-96">
|
||||
<x-forms.checkbox instantSave id="do_not_track"
|
||||
helper="Opt out of anonymous usage tracking. When enabled, this instance will not report to coolify.io's installation count and will not send error reports to help improve Coolify."
|
||||
@@ -50,6 +70,17 @@
|
||||
environments!
|
||||
</x-callout>
|
||||
@endif
|
||||
<h4 class="pt-4">MCP Server</h4>
|
||||
<div class="md:w-96">
|
||||
<x-forms.checkbox instantSave id="is_mcp_server_enabled" label="Enable MCP Server"
|
||||
helper="Exposes a Streamable HTTP Model Context Protocol endpoint at /mcp for AI clients (Claude Desktop, Cursor, etc.). Authenticates via Sanctum API tokens (Security > API Tokens). Requires API Access to be enabled." />
|
||||
</div>
|
||||
@if ($is_mcp_server_enabled)
|
||||
<x-callout type="info" title="MCP Endpoint" class="mt-2">
|
||||
Endpoint: <code>{{ url('/mcp') }}</code><br>
|
||||
Authenticate with <code>Authorization: Bearer <token></code> using a token created in Security » API Tokens.
|
||||
</x-callout>
|
||||
@endif
|
||||
<h4 class="pt-4">UI Settings</h4>
|
||||
<div class="md:w-96">
|
||||
<x-forms.checkbox instantSave id="is_wire_navigate_enabled" label="SPA Navigation"
|
||||
@@ -95,4 +126,4 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
@else
|
||||
<form wire:submit='submit' class="flex flex-col gap-2">
|
||||
<x-forms.textarea canGate="update" :canResource="$environment" rows="20" class="whitespace-pre-wrap" id="variables" wire:model="variables"
|
||||
<x-forms.textarea canGate="update" :canResource="$environment" rows="20" class="whitespace-pre-wrap" id="variables" wire:model="variables" monospace
|
||||
label="Environment Shared Variables"></x-forms.textarea>
|
||||
<x-forms.button canGate="update" :canResource="$environment" type="submit" class="btn btn-primary">Save All Environment Variables</x-forms.button>
|
||||
</form>
|
||||
|
||||
@@ -5,27 +5,33 @@
|
||||
<div class="flex items-start gap-2">
|
||||
<h1>Shared Variables</h1>
|
||||
</div>
|
||||
<div class="subtitle">Set Team / Project / Environment wide variables.</div>
|
||||
<div class="subtitle">Set Team / Project / Environment / Server wide variables.</div>
|
||||
|
||||
<div class="flex flex-col gap-2 -mt-1">
|
||||
<a class="coolbox group" href="{{ route('shared-variables.team.index') }}" {{ wireNavigate() }}>
|
||||
<div class="flex flex-col justify-center mx-6">
|
||||
<div class="box-title">Team wide</div>
|
||||
<div class="box-description">Usable for all resources in a team.</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="coolbox group" href="{{ route('shared-variables.project.index') }}" {{ wireNavigate() }}>
|
||||
<div class="flex flex-col justify-center mx-6">
|
||||
<div class="box-title">Project wide</div>
|
||||
<div class="box-description">Usable for all resources in a project.</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="coolbox group" href="{{ route('shared-variables.environment.index') }}" {{ wireNavigate() }}>
|
||||
<div class="flex flex-col justify-center mx-6">
|
||||
<div class="box-title">Environment wide</div>
|
||||
<div class="box-description">Usable for all resources in an environment.</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="flex flex-col gap-2 -mt-1">
|
||||
<a class="coolbox group" href="{{ route('shared-variables.team.index') }}" {{ wireNavigate() }}>
|
||||
<div class="flex flex-col justify-center mx-6">
|
||||
<div class="box-title">Team wide</div>
|
||||
<div class="box-description">Usable for all resources in a team.</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="coolbox group" href="{{ route('shared-variables.project.index') }}" {{ wireNavigate() }}>
|
||||
<div class="flex flex-col justify-center mx-6">
|
||||
<div class="box-title">Project wide</div>
|
||||
<div class="box-description">Usable for all resources in a project.</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="coolbox group" href="{{ route('shared-variables.environment.index') }}" {{ wireNavigate() }}>
|
||||
<div class="flex flex-col justify-center mx-6">
|
||||
<div class="box-title">Environment wide</div>
|
||||
<div class="box-description">Usable for all resources in an environment.</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="coolbox group" href="{{ route('shared-variables.server.index') }}" {{ wireNavigate() }}>
|
||||
<div class="flex flex-col justify-center mx-6">
|
||||
<div class="box-title">Server wide</div>
|
||||
<div class="box-description">Usable for all resources in a server.</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
@else
|
||||
<form wire:submit='submit' class="flex flex-col gap-2">
|
||||
<x-forms.textarea canGate="update" :canResource="$project" rows="20" class="whitespace-pre-wrap" id="variables" wire:model="variables"
|
||||
<x-forms.textarea canGate="update" :canResource="$project" rows="20" class="whitespace-pre-wrap" id="variables" wire:model="variables" monospace
|
||||
label="Project Shared Variables"></x-forms.textarea>
|
||||
<x-forms.button canGate="update" :canResource="$project" type="submit" class="btn btn-primary">Save All Environment Variables</x-forms.button>
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<div>
|
||||
<x-slot:title>
|
||||
Server Variables | Coolify
|
||||
</x-slot>
|
||||
<div class="flex gap-2">
|
||||
<h1>Servers</h1>
|
||||
</div>
|
||||
<div class="subtitle">List of your servers.</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
@forelse ($servers as $server)
|
||||
<a class="coolbox group"
|
||||
href="{{ route('shared-variables.server.show', ['server_uuid' => data_get($server, 'uuid')]) }}" {{ wireNavigate() }}>
|
||||
<div class="flex flex-col justify-center mx-6 ">
|
||||
<div class="box-title">{{ $server->name }}</div>
|
||||
<div class="box-description ">
|
||||
{{ $server->description }}</div>
|
||||
</div>
|
||||
</a>
|
||||
@empty
|
||||
<div>
|
||||
<div>No server found.</div>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,36 @@
|
||||
<div>
|
||||
<x-slot:title>
|
||||
Server Variable | Coolify
|
||||
</x-slot>
|
||||
<div class="flex gap-2 items-center">
|
||||
<h1>Shared Variables for {{ data_get($server, 'name') }}</h1>
|
||||
@can('update', $server)
|
||||
<x-modal-input buttonTitle="+ Add" title="New Shared Variable">
|
||||
<livewire:project.shared.environment-variable.add :shared="true" />
|
||||
</x-modal-input>
|
||||
@endcan
|
||||
<x-forms.button canGate="update" :canResource="$server" wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1 subtitle">
|
||||
<div>You can use these variables anywhere with</div>
|
||||
<div class="dark:text-warning text-coollabs">@{{ server.VARIABLENAME }} </div>
|
||||
<x-helper
|
||||
helper="More info <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/environment-variables#shared-variables' target='_blank'>here</a>."></x-helper>
|
||||
</div>
|
||||
@if ($view === 'normal')
|
||||
<div class="flex flex-col gap-2">
|
||||
@forelse ($server->environment_variables->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])->sortBy('key') as $env)
|
||||
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
|
||||
:env="$env" type="server" />
|
||||
@empty
|
||||
<div>No environment variables found.</div>
|
||||
@endforelse
|
||||
</div>
|
||||
@else
|
||||
<form wire:submit='submit' class="flex flex-col gap-2">
|
||||
<x-forms.textarea canGate="update" :canResource="$server" rows="20" class="whitespace-pre-wrap" id="variables" wire:model="variables"
|
||||
label="Server Shared Variables"></x-forms.textarea>
|
||||
<x-forms.button canGate="update" :canResource="$server" type="submit" class="btn btn-primary">Save All Environment Variables</x-forms.button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
@else
|
||||
<form wire:submit='submit' class="flex flex-col gap-2">
|
||||
<x-forms.textarea canGate="update" :canResource="$team" rows="20" class="whitespace-pre-wrap" id="variables" wire:model="variables"
|
||||
<x-forms.textarea canGate="update" :canResource="$team" rows="20" class="whitespace-pre-wrap" id="variables" wire:model="variables" monospace
|
||||
label="Team Shared Variables"></x-forms.textarea>
|
||||
<x-forms.button canGate="update" :canResource="$team" type="submit" class="btn btn-primary">Save All Environment Variables</x-forms.button>
|
||||
</form>
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<h1>GitHub App</h1>
|
||||
<div class="flex gap-2">
|
||||
@if (data_get($github_app, 'installation_id'))
|
||||
<x-forms.button canGate="update" :canResource="$github_app" type="submit">Save</x-forms.button>
|
||||
<x-forms.button canGate="update" :canResource="$github_app" type="submit"
|
||||
:disabled="$activeTab !== 'general'">Save</x-forms.button>
|
||||
@endif
|
||||
@can('delete', $github_app)
|
||||
@if ($applications->count() > 0)
|
||||
@@ -39,170 +40,187 @@
|
||||
Install Repositories on GitHub
|
||||
</a>
|
||||
@else
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-end gap-2 w-full">
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="name" label="App Name" />
|
||||
<x-forms.button canGate="update" :canResource="$github_app" wire:click.prevent="updateGithubAppName">
|
||||
Sync Name
|
||||
</x-forms.button>
|
||||
@can('update', $github_app)
|
||||
<a href="{{ $this->getGithubAppNameUpdatePath() }}">
|
||||
<x-forms.button
|
||||
class="bg-transparent border-transparent hover:bg-transparent hover:border-transparent hover:underline">
|
||||
Rename
|
||||
<x-external-link />
|
||||
</x-forms.button>
|
||||
</a>
|
||||
<a href="{{ getInstallationPath($github_app) }}" class="w-fit">
|
||||
<x-forms.button
|
||||
class="bg-transparent border-transparent hover:bg-transparent hover:border-transparent hover:underline whitespace-nowrap">
|
||||
Update Repositories
|
||||
<div class="navbar-main">
|
||||
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
||||
<a class="{{ request()->routeIs('source.github.show') ? 'dark:text-white' : '' }}"
|
||||
{{ wireNavigate() }}
|
||||
href="{{ route('source.github.show', ['github_app_uuid' => $github_app->uuid]) }}">
|
||||
General
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('source.github.permissions') ? 'dark:text-white' : '' }}"
|
||||
{{ wireNavigate() }}
|
||||
href="{{ route('source.github.permissions', ['github_app_uuid' => $github_app->uuid]) }}">
|
||||
Permissions
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('source.github.resources') ? 'dark:text-white' : '' }}"
|
||||
{{ wireNavigate() }}
|
||||
href="{{ route('source.github.resources', ['github_app_uuid' => $github_app->uuid]) }}">
|
||||
Resources
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="pt-4">
|
||||
<div class="flex flex-col gap-2" @if ($activeTab !== 'general') hidden @endif>
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-end gap-2 w-full">
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="name" label="App Name" />
|
||||
<x-forms.button canGate="update" :canResource="$github_app" wire:click.prevent="updateGithubAppName">
|
||||
Sync Name
|
||||
</x-forms.button>
|
||||
@can('update', $github_app)
|
||||
<a href="{{ $this->getGithubAppNameUpdatePath() }}">
|
||||
<x-forms.button canGate="update" :canResource="$github_app"
|
||||
class="bg-transparent border-transparent hover:bg-transparent hover:border-transparent hover:underline">
|
||||
Rename
|
||||
<x-external-link />
|
||||
</x-forms.button>
|
||||
</a>
|
||||
<a href="{{ getInstallationPath($github_app) }}" class="w-fit">
|
||||
<x-forms.button canGate="update" :canResource="$github_app"
|
||||
class="bg-transparent border-transparent hover:bg-transparent hover:border-transparent hover:underline whitespace-nowrap">
|
||||
Update Repositories
|
||||
<x-external-link />
|
||||
</x-forms.button>
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="organization" label="Organization"
|
||||
placeholder="If empty, personal user will be used" />
|
||||
@if (!isCloud())
|
||||
<div class="w-48">
|
||||
<x-forms.checkbox canGate="update" :canResource="$github_app" label="System Wide?"
|
||||
helper="If checked, this GitHub App will be available for everyone in this Coolify instance."
|
||||
instantSave id="isSystemWide" />
|
||||
</div>
|
||||
@if ($isSystemWide)
|
||||
<x-callout type="warning" title="Not Recommended">
|
||||
System-wide GitHub Apps are shared across all teams on this Coolify instance. This means any team can use this GitHub App to deploy applications from your repositories. For better security and isolation, it's recommended to create team-specific GitHub Apps instead.
|
||||
</x-callout>
|
||||
@endif
|
||||
@endif
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="htmlUrl" label="HTML Url" />
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="apiUrl" label="API Url" />
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="customUser" label="User"
|
||||
required />
|
||||
<x-forms.input canGate="update" :canResource="$github_app" type="number" id="customPort"
|
||||
label="Port" required />
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$github_app" type="number" id="appId"
|
||||
label="App Id" required />
|
||||
<x-forms.input canGate="update" :canResource="$github_app" type="number"
|
||||
id="installationId" label="Installation Id" required />
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="clientId" label="Client Id"
|
||||
type="password" required />
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="clientSecret"
|
||||
label="Client Secret" type="password" required />
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="webhookSecret"
|
||||
label="Webhook Secret" type="password" required />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.select canGate="update" :canResource="$github_app" id="privateKeyId"
|
||||
label="Private Key" required>
|
||||
@if (blank($github_app->private_key_id))
|
||||
<option value="0" selected>Select a private key</option>
|
||||
@endif
|
||||
@foreach ($privateKeys as $privateKey)
|
||||
<option value="{{ $privateKey->id }}">{{ $privateKey->name }}</option>
|
||||
@endforeach
|
||||
</x-forms.select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2" @if ($activeTab !== 'permissions') hidden @endif>
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-end gap-2">
|
||||
<h2>Permissions</h2>
|
||||
@can('view', $github_app)
|
||||
<x-forms.button canGate="view" :canResource="$github_app" wire:click.prevent="checkPermissions">Refetch</x-forms.button>
|
||||
<a href="{{ getPermissionsPath($github_app) }}">
|
||||
<x-forms.button canGate="view" :canResource="$github_app" class="bg-transparent border-transparent hover:bg-transparent hover:border-transparent hover:underline">
|
||||
Update
|
||||
<x-external-link />
|
||||
</x-forms.button>
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="organization" label="Organization"
|
||||
placeholder="If empty, personal user will be used" />
|
||||
@if (!isCloud())
|
||||
<div class="w-48">
|
||||
<x-forms.checkbox canGate="update" :canResource="$github_app" label="System Wide?"
|
||||
helper="If checked, this GitHub App will be available for everyone in this Coolify instance."
|
||||
instantSave id="isSystemWide" />
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<x-forms.input canGate="view" :canResource="$github_app" id="contents" helper="read - mandatory." label="Content" readonly
|
||||
placeholder="N/A" />
|
||||
<x-forms.input canGate="view" :canResource="$github_app" id="metadata" helper="read - mandatory." label="Metadata" readonly
|
||||
placeholder="N/A" />
|
||||
<x-forms.input canGate="view" :canResource="$github_app" id="pullRequests"
|
||||
helper="write access needed to use deployment status update in previews."
|
||||
label="Pull Request" readonly placeholder="N/A" />
|
||||
</div>
|
||||
@if ($isSystemWide)
|
||||
<x-callout type="warning" title="Not Recommended">
|
||||
System-wide GitHub Apps are shared across all teams on this Coolify instance. This means any team can use this GitHub App to deploy applications from your repositories. For better security and isolation, it's recommended to create team-specific GitHub Apps instead.
|
||||
</x-callout>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col" @if ($activeTab !== 'resources') hidden @endif x-data="{ search: '' }">
|
||||
@if ($applications->isEmpty())
|
||||
<div class="py-4 text-sm opacity-70">
|
||||
No resources are currently using this GitHub App.
|
||||
</div>
|
||||
@else
|
||||
<x-forms.input canGate="view" :canResource="$github_app" placeholder="Search resources..." x-model="search" id="null" />
|
||||
<div class="overflow-x-auto pt-4">
|
||||
<div class="inline-block min-w-full">
|
||||
<div class="overflow-hidden">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Project</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Environment</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Name</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
@foreach ($applications->sortBy('name', SORT_NATURAL) as $resource)
|
||||
@php
|
||||
$projectName = (string) data_get($resource->project(), 'name');
|
||||
$environmentName = (string) data_get($resource, 'environment.name');
|
||||
$resourceName = (string) $resource->name;
|
||||
$resourceType = (string) str($resource->type())->headline();
|
||||
@endphp
|
||||
<tr class="dark:hover:bg-coolgray-300 hover:bg-neutral-100"
|
||||
x-show="search === ''
|
||||
|| '{{ strtolower(addslashes($projectName)) }}'.includes(search.toLowerCase())
|
||||
|| '{{ strtolower(addslashes($environmentName)) }}'.includes(search.toLowerCase())
|
||||
|| '{{ strtolower(addslashes($resourceName)) }}'.includes(search.toLowerCase())
|
||||
|| '{{ strtolower(addslashes($resourceType)) }}'.includes(search.toLowerCase())">
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ $projectName }}
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ $environmentName }}
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
<a {{ wireNavigate() }} href="{{ $resource->link() }}">
|
||||
{{ $resourceName }}
|
||||
<x-internal-link />
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ $resourceType }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="htmlUrl" label="HTML Url" />
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="apiUrl" label="API Url" />
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="customUser" label="User"
|
||||
required />
|
||||
<x-forms.input canGate="update" :canResource="$github_app" type="number" id="customPort"
|
||||
label="Port" required />
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$github_app" type="number" id="appId"
|
||||
label="App Id" required />
|
||||
<x-forms.input canGate="update" :canResource="$github_app" type="number"
|
||||
id="installationId" label="Installation Id" required />
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="clientId" label="Client Id"
|
||||
type="password" required />
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="clientSecret"
|
||||
label="Client Secret" type="password" required />
|
||||
<x-forms.input canGate="update" :canResource="$github_app" id="webhookSecret"
|
||||
label="Webhook Secret" type="password" required />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.select canGate="update" :canResource="$github_app" id="privateKeyId"
|
||||
label="Private Key" required>
|
||||
@if (blank($github_app->private_key_id))
|
||||
<option value="0" selected>Select a private key</option>
|
||||
@endif
|
||||
@foreach ($privateKeys as $privateKey)
|
||||
<option value="{{ $privateKey->id }}">{{ $privateKey->name }}</option>
|
||||
@endforeach
|
||||
</x-forms.select>
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-end gap-2">
|
||||
<h2 class="pt-4">Permissions</h2>
|
||||
@can('view', $github_app)
|
||||
<x-forms.button wire:click.prevent="checkPermissions">Refetch</x-forms.button>
|
||||
<a href="{{ getPermissionsPath($github_app) }}">
|
||||
<x-forms.button>
|
||||
Update
|
||||
<x-external-link />
|
||||
</x-forms.button>
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<x-forms.input id="contents" helper="read - mandatory." label="Content" readonly
|
||||
placeholder="N/A" />
|
||||
<x-forms.input id="metadata" helper="read - mandatory." label="Metadata" readonly
|
||||
placeholder="N/A" />
|
||||
{{-- <x-forms.input id="administration"
|
||||
helper="read:write access needed to setup servers as GitHub Runner." label="Administration"
|
||||
readonly placeholder="N/A" /> --}}
|
||||
<x-forms.input id="pullRequests"
|
||||
helper="write access needed to use deployment status update in previews."
|
||||
label="Pull Request" readonly placeholder="N/A" />
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</form>
|
||||
@if (data_get($github_app, 'installation_id'))
|
||||
<div class="w-full pt-10">
|
||||
<div class="h-full">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex gap-2">
|
||||
<h2>Resources</h2>
|
||||
</div>
|
||||
<div class="pb-4 title">Here you can find all resources that are using this source.</div>
|
||||
</div>
|
||||
@if ($applications->isEmpty())
|
||||
<div class="py-4 text-sm opacity-70">
|
||||
No resources are currently using this GitHub App.
|
||||
</div>
|
||||
@else
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col">
|
||||
<div class="overflow-x-auto">
|
||||
<div class="inline-block min-w-full">
|
||||
<div class="overflow-hidden">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">
|
||||
Project
|
||||
</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">
|
||||
Environment</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Name
|
||||
</th>
|
||||
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Type
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
@foreach ($applications->sortBy('name',SORT_NATURAL) as $resource)
|
||||
<tr>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ data_get($resource->project(), 'name') }}
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ data_get($resource, 'environment.name') }}
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap"><a
|
||||
class=""
|
||||
{{ wireNavigate() }}
|
||||
href="{{ $resource->link() }}">{{ $resource->name }}
|
||||
<x-internal-link /></a>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||
{{ str($resource->type())->headline() }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-2 pb-4">
|
||||
<h1>GitHub App</h1>
|
||||
@@ -216,89 +234,138 @@
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-center min-h-[calc(100vh-12rem)]">
|
||||
<div class="mx-auto grid w-full max-w-5xl grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
@can('create', $github_app)
|
||||
<h3>Manual Installation</h3>
|
||||
<div class="flex gap-2 items-center">
|
||||
If you want to fill the form manually, you can continue below. Only for advanced users.
|
||||
<x-forms.button wire:click.prevent="createGithubAppManually">
|
||||
Continue
|
||||
</x-forms.button>
|
||||
</div>
|
||||
<h3>Automated Installation</h3>
|
||||
<div class=" pb-5 rounded-sm alert-error">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<span>You must complete this step before you can use this source!</span>
|
||||
<section class="box-without-bg flex-col gap-4 p-6 h-full transition-all duration-200"
|
||||
x-data="{
|
||||
webhookEndpoint: $wire.entangle('webhook_endpoint').live,
|
||||
useCustomWebhookEndpoint: $wire.entangle('use_custom_webhook_endpoint').live,
|
||||
customWebhookEndpoint: $wire.entangle('custom_webhook_endpoint').live,
|
||||
}">
|
||||
<div class="flex flex-col gap-4 text-left h-full">
|
||||
<div class="flex items-center justify-between">
|
||||
<svg class="size-10" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />
|
||||
</svg>
|
||||
<span
|
||||
class="px-2 py-1 text-xs font-bold uppercase tracking-wide bg-coollabs/10 dark:bg-warning/20 text-coollabs dark:text-warning rounded">
|
||||
Recommended
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold mb-2">Automated Installation</h3>
|
||||
<p class="text-sm dark:text-neutral-400">
|
||||
Register a GitHub App via GitHub's manifest flow. Permissions and webhooks are pre-configured.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 pt-4 border-t border-neutral-200 dark:border-coolgray-400">
|
||||
@if (!isCloud() || isDev())
|
||||
<x-forms.checkbox canGate="create" :canResource="$github_app"
|
||||
x-model="useCustomWebhookEndpoint" id="use_custom_webhook_endpoint"
|
||||
label="Use custom webhook endpoint"
|
||||
helper="Enable this when the public URL GitHub should call differs from Coolify's configured URL, for example behind Cloudflare Tunnel." />
|
||||
<div x-show="!useCustomWebhookEndpoint">
|
||||
<x-forms.select canGate="create" :canResource="$github_app"
|
||||
wire:model.live='webhook_endpoint' x-model="webhookEndpoint"
|
||||
label="Selected endpoint"
|
||||
helper="GitHub will use this endpoint unless custom mode is enabled.">
|
||||
@if ($fqdn)
|
||||
<option value="{{ $fqdn }}">Use {{ $fqdn }}</option>
|
||||
@endif
|
||||
@if ($ipv4)
|
||||
<option value="{{ $ipv4 }}">Use {{ $ipv4 }}</option>
|
||||
@endif
|
||||
@if ($ipv6)
|
||||
<option value="{{ $ipv6 }}">Use {{ $ipv6 }}</option>
|
||||
@endif
|
||||
@if (config('app.url'))
|
||||
<option value="{{ config('app.url') }}">Use {{ config('app.url') }}</option>
|
||||
@endif
|
||||
</x-forms.select>
|
||||
</div>
|
||||
<div x-cloak x-show="useCustomWebhookEndpoint">
|
||||
<x-forms.input canGate="create" :canResource="$github_app"
|
||||
x-model="customWebhookEndpoint" id="custom_webhook_endpoint" type="url"
|
||||
label="Custom endpoint" placeholder="https://coolify.example.com"
|
||||
helper="GitHub will use this custom public URL. Do not include /webhooks." />
|
||||
</div>
|
||||
@else
|
||||
<div class="text-sm dark:text-neutral-400">You need to register a GitHub App before using this source.</div>
|
||||
@endif
|
||||
|
||||
<div class="flex w-full flex-col gap-2">
|
||||
<x-forms.checkbox canGate="create" :canResource="$github_app" disabled id="default_permissions" label="Mandatory"
|
||||
helper="Contents: read<br>Metadata: read<br>Email: read" />
|
||||
<x-forms.checkbox canGate="create" :canResource="$github_app" id="preview_deployment_permissions" label="Preview Deployments"
|
||||
helper="Necessary for updating pull requests with useful comments (deployment status, links, etc.)<br><br>Pull Request: read & write" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-auto pt-2">
|
||||
<x-forms.button canGate="create" :canResource="$github_app" class="w-full justify-center" isHighlighted
|
||||
x-on:click.prevent="createGithubApp(webhookEndpoint, useCustomWebhookEndpoint, customWebhookEndpoint, {{ Illuminate\Support\Js::from($preview_deployment_permissions) }}, {{ Illuminate\Support\Js::from($administration) }})">
|
||||
Register Now
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="box-without-bg flex-col gap-4 p-6 h-full transition-all duration-200">
|
||||
<div class="flex flex-col gap-4 text-left h-full">
|
||||
<div class="flex items-center justify-between">
|
||||
<svg class="size-10" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z" />
|
||||
</svg>
|
||||
<span
|
||||
class="px-2 py-1 text-xs font-bold uppercase tracking-wide bg-neutral-100 dark:bg-coolgray-300 dark:text-neutral-400 rounded">
|
||||
Advanced
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold mb-2">Manual Installation</h3>
|
||||
<p class="text-sm dark:text-neutral-400">
|
||||
Fill the GitHub App form manually. For self-hosted GitHub Enterprise or custom permission setups.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-auto pt-2">
|
||||
<x-forms.button canGate="create" :canResource="$github_app" class="w-full justify-center" wire:click.prevent="createGithubAppManually">
|
||||
Continue
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@else
|
||||
<div class="pb-10">
|
||||
<x-callout type="danger" title="Insufficient Permissions">
|
||||
You don't have permission to create new GitHub Apps. Please contact your team administrator.
|
||||
</x-callout>
|
||||
</div>
|
||||
@endcan
|
||||
<div class="flex flex-col">
|
||||
<div class="pb-10">
|
||||
@can('create', $github_app)
|
||||
@if (!isCloud() || isDev())
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-end gap-2">
|
||||
<x-forms.select wire:model.live='webhook_endpoint' label="Webhook Endpoint"
|
||||
helper="All Git webhooks will be sent to this endpoint. <br><br>If you would like to use domain instead of IP address, set your Coolify instance's FQDN in the Settings menu.">
|
||||
@if ($fqdn)
|
||||
<option value="{{ $fqdn }}">Use {{ $fqdn }}</option>
|
||||
@endif
|
||||
@if ($ipv4)
|
||||
<option value="{{ $ipv4 }}">Use {{ $ipv4 }}</option>
|
||||
@endif
|
||||
@if ($ipv6)
|
||||
<option value="{{ $ipv6 }}">Use {{ $ipv6 }}</option>
|
||||
@endif
|
||||
@if (config('app.url'))
|
||||
<option value="{{ config('app.url') }}">Use {{ config('app.url') }}</option>
|
||||
@endif
|
||||
</x-forms.select>
|
||||
<x-forms.button isHighlighted
|
||||
x-on:click.prevent="createGithubApp('{{ $webhook_endpoint }}','{{ $preview_deployment_permissions }}',{{ $administration }})">
|
||||
Register Now
|
||||
</x-forms.button>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<h2>Register a GitHub App</h2>
|
||||
<x-forms.button isHighlighted
|
||||
x-on:click.prevent="createGithubApp('{{ $webhook_endpoint }}','{{ $preview_deployment_permissions }}',{{ $administration }})">
|
||||
Register Now
|
||||
</x-forms.button>
|
||||
</div>
|
||||
<div>You need to register a GitHub App before using this source.</div>
|
||||
@endif
|
||||
|
||||
<div class="flex flex-col gap-2 pt-4 w-96">
|
||||
<x-forms.checkbox disabled id="default_permissions" label="Mandatory"
|
||||
helper="Contents: read<br>Metadata: read<br>Email: read" />
|
||||
<x-forms.checkbox id="preview_deployment_permissions" label="Preview Deployments "
|
||||
helper="Necessary for updating pull requests with useful comments (deployment status, links, etc.)<br><br>Pull Request: read & write" />
|
||||
{{-- <x-forms.checkbox id="administration" label="Administration (for Github Runners)"
|
||||
helper="Necessary for adding Github Runners to repositories.<br><br>Administration: read & write" /> --}}
|
||||
</div>
|
||||
@else
|
||||
<x-callout type="danger" title="Insufficient Permissions">
|
||||
You don't have permission to create new GitHub Apps. Please contact your team administrator.
|
||||
</x-callout>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function createGithubApp(webhook_endpoint, preview_deployment_permissions, administration) {
|
||||
function createGithubApp(webhook_endpoint, use_custom_webhook_endpoint, custom_webhook_endpoint, preview_deployment_permissions, administration) {
|
||||
const {
|
||||
organization,
|
||||
uuid,
|
||||
html_url
|
||||
} = @json($github_app);
|
||||
if (!webhook_endpoint) {
|
||||
alert('Please select a webhook endpoint.');
|
||||
} = @js($github_app->only(['organization', 'html_url']));
|
||||
const selectedEndpoint = webhook_endpoint ? webhook_endpoint.trim() : '';
|
||||
const customEndpoint = custom_webhook_endpoint ? custom_webhook_endpoint.trim() : '';
|
||||
if (use_custom_webhook_endpoint && !customEndpoint) {
|
||||
alert('Please enter a custom webhook endpoint.');
|
||||
return;
|
||||
}
|
||||
let baseUrl = webhook_endpoint;
|
||||
if (!use_custom_webhook_endpoint && !selectedEndpoint) {
|
||||
alert('Please enter a webhook endpoint.');
|
||||
return;
|
||||
}
|
||||
let baseUrl = (use_custom_webhook_endpoint ? customEndpoint : selectedEndpoint).replace(/\/+$/, '');
|
||||
const name = @js($name);
|
||||
const manifestState = @js($manifestState);
|
||||
const isDev = @js(config('app.env')) ===
|
||||
'local';
|
||||
const devWebhook = @js(config('constants.webhooks.dev_webhook'));
|
||||
@@ -333,14 +400,14 @@
|
||||
callback_urls: [`${baseUrl}/login/github/app`],
|
||||
public: false,
|
||||
request_oauth_on_install: false,
|
||||
setup_url: `${webhookBaseUrl}/source/github/install?source=${uuid}`,
|
||||
setup_url: `${webhookBaseUrl}/source/github/install`,
|
||||
setup_on_update: true,
|
||||
default_permissions,
|
||||
default_events
|
||||
};
|
||||
const form = document.createElement('form');
|
||||
form.setAttribute('method', 'post');
|
||||
form.setAttribute('action', `${html_url}/${path}?state=${uuid}`);
|
||||
form.setAttribute('action', `${html_url}/${path}?state=${manifestState}`);
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('id', 'manifest');
|
||||
input.setAttribute('name', 'manifest');
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
<span class="dark:text-white" x-text="fmt(preview?.recurring_tax)"></span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-6 text-sm font-bold pt-1.5 border-t dark:border-coolgray-400 border-neutral-200">
|
||||
<span class="dark:text-white">Total / {{ $billingInterval === 'yearly' ? 'year' : 'month' }}</span>
|
||||
<span class="dark:text-white">Total / <span x-text="preview?.billing_interval === 'year' ? 'year' : 'month'">month</span></span>
|
||||
<span class="dark:text-white" x-text="fmt(preview?.recurring_total)"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -249,23 +249,23 @@
|
||||
{{-- Refund --}}
|
||||
<section>
|
||||
<h3 class="pb-2">Refund</h3>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@if ($refundCheckLoading)
|
||||
<x-forms.button disabled>Request Full Refund</x-forms.button>
|
||||
@elseif ($isRefundEligible && !currentTeam()->subscription->stripe_cancel_at_period_end)
|
||||
<x-modal-confirmation title="Request Full Refund?" buttonTitle="Request Full Refund"
|
||||
isErrorButton submitAction="refundSubscription"
|
||||
:actions="[
|
||||
'Your latest payment will be fully refunded.',
|
||||
'Your subscription will be cancelled immediately.',
|
||||
'All servers will be deactivated.',
|
||||
]" confirmationText="{{ currentTeam()->name }}"
|
||||
confirmationLabel="Enter your team name to confirm" shortConfirmationLabel="Team Name"
|
||||
step2ButtonText="Confirm Refund & Cancel" />
|
||||
@else
|
||||
<x-forms.button disabled>Request Full Refund</x-forms.button>
|
||||
@endif
|
||||
</div>
|
||||
@if ($refundCheckLoading || ($isRefundEligible && !currentTeam()->subscription->stripe_cancel_at_period_end))
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@if ($refundCheckLoading)
|
||||
<x-forms.button disabled>Request Full Refund</x-forms.button>
|
||||
@else
|
||||
<x-modal-confirmation title="Request Full Refund?" buttonTitle="Request Full Refund"
|
||||
isErrorButton submitAction="refundSubscription"
|
||||
:actions="[
|
||||
'Your latest payment will be fully refunded.',
|
||||
'Your subscription will be cancelled immediately.',
|
||||
'All servers will be deactivated.',
|
||||
]" confirmationText="{{ currentTeam()->name }}"
|
||||
confirmationLabel="Enter your team name to confirm" shortConfirmationLabel="Team Name"
|
||||
step2ButtonText="Confirm Refund & Cancel" />
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
<p class="mt-2 text-sm text-neutral-500">
|
||||
@if ($refundCheckLoading)
|
||||
Checking refund eligibility...
|
||||
|
||||
@@ -1,6 +1,49 @@
|
||||
<x-forms.select wire:model.live="selectedTeamId">
|
||||
<option value="default" disabled selected>Switch team</option>
|
||||
@foreach (auth()->user()->teams as $team)
|
||||
<option value="{{ $team->id }}">{{ $team->name }}</option>
|
||||
@endforeach
|
||||
</x-forms.select>
|
||||
@php
|
||||
$currentTeam = auth()->user()->currentTeam();
|
||||
$teamInitial = strtoupper(mb_substr($currentTeam->name, 0, 1));
|
||||
@endphp
|
||||
<div>
|
||||
<div :class="collapsed && 'lg:hidden'">
|
||||
<x-forms.select wire:model.live="selectedTeamId">
|
||||
<option value="default" disabled selected>Switch team</option>
|
||||
@foreach (auth()->user()->teams as $team)
|
||||
<option value="{{ $team->id }}">{{ $team->name }}</option>
|
||||
@endforeach
|
||||
</x-forms.select>
|
||||
</div>
|
||||
<div class="hidden"
|
||||
:class="collapsed && 'lg:block'"
|
||||
x-data="{
|
||||
teamOpen: false,
|
||||
teamX: 0,
|
||||
teamY: 0,
|
||||
openTeamMenu(ev) {
|
||||
const rect = ev.currentTarget.getBoundingClientRect();
|
||||
this.teamX = rect.right + 8;
|
||||
this.teamY = rect.top;
|
||||
this.teamOpen = !this.teamOpen;
|
||||
}
|
||||
}">
|
||||
<button @click="openTeamMenu($event)" type="button"
|
||||
title="Team: {{ $currentTeam->name }}"
|
||||
class="flex items-center justify-center w-8 h-8 p-0 text-sm font-semibold text-coollabs dark:text-warning bg-neutral-100 dark:bg-coolgray-200 hover:bg-neutral-200 dark:hover:bg-coolgray-300 rounded-sm cursor-pointer transition-colors">
|
||||
{{ $teamInitial }}
|
||||
</button>
|
||||
<div x-show="teamOpen"
|
||||
@click.outside="teamOpen = false"
|
||||
x-transition.opacity.duration.100ms
|
||||
x-cloak
|
||||
:style="`left: ${teamX}px; top: ${teamY}px;`"
|
||||
class="fixed z-[100] min-w-48 max-h-72 overflow-y-auto bg-white dark:bg-coolgray-100 border border-neutral-300 dark:border-coolgray-200 rounded-md shadow-lg py-1">
|
||||
<div class="px-3 py-1.5 text-xs font-semibold text-neutral-500 dark:text-neutral-400 border-b border-neutral-200 dark:border-coolgray-200">Switch team</div>
|
||||
@foreach (auth()->user()->teams as $team)
|
||||
<button type="button"
|
||||
wire:click="switch_to({{ $team->id }})"
|
||||
@click="teamOpen = false"
|
||||
class="w-full px-3 py-1.5 text-left text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200 dark:text-white {{ $team->id === $currentTeam->id ? 'font-semibold text-coollabs dark:text-warning' : '' }}">
|
||||
{{ $team->name }}
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6,17 +6,17 @@
|
||||
})">
|
||||
@if ($isUpgradeAvailable)
|
||||
<div :class="{ 'z-40': modalOpen }" class="relative w-auto h-auto">
|
||||
<button class="menu-item" @click="modalOpen=true" x-show="showProgress">
|
||||
<button title="Upgrade in progress" aria-label="Upgrade in progress" class="menu-item" @click="modalOpen=true" x-show="showProgress">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6 text-pink-500 transition-colors hover:text-pink-300 lds-heart" viewBox="0 0 24 24"
|
||||
class="text-pink-500 transition-colors menu-item-icon hover:text-pink-300 lds-heart" 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="M19.5 13.572l-7.5 7.428l-7.5 -7.428m0 0a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" />
|
||||
</svg>
|
||||
In progress
|
||||
<span class="text-left menu-item-label" :class="collapsed && 'lg:hidden'">In progress</span>
|
||||
</button>
|
||||
<button class="menu-item cursor-pointer" @click="modalOpen=true" x-show="!showProgress">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-pink-500 transition-colors hover:text-pink-300"
|
||||
<button title="Upgrade" aria-label="Upgrade" class="menu-item cursor-pointer" @click="modalOpen=true" x-show="!showProgress">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="text-pink-500 transition-colors menu-item-icon hover:text-pink-300"
|
||||
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" />
|
||||
@@ -25,7 +25,7 @@
|
||||
<path d="M9 21h6" />
|
||||
<path d="M9 18h6" />
|
||||
</svg>
|
||||
Upgrade
|
||||
<span class="text-left menu-item-label" :class="collapsed && 'lg:hidden'">Upgrade</span>
|
||||
</button>
|
||||
<template x-teleport="body">
|
||||
<div x-show="modalOpen"
|
||||
|
||||
Reference in new issue
Block a user