Merge branch 'next' into docker-network-aliases

This commit is contained in:
Andras Bacsai
2025-04-08 13:27:59 +02:00
committed by GitHub
193 changed files with 11890 additions and 3393 deletions

View File

@@ -46,7 +46,7 @@ body {
.input,
.select {
@apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 block w-full py-1.5 rounded border-0 text-sm ring-1 ring-inset;
@apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 block w-full py-1.5 rounded border-0 text-sm ring-1 ring-inset disabled:dark:bg-coolgray-100/40 disabled:dark:ring-transparent disabled:bg-neutral-200 disabled:text-neutral-500;
}
.select {
@@ -97,6 +97,7 @@ label {
@apply dark:text-neutral-400;
}
table {
@apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300;
}

View File

@@ -1,10 +1,3 @@
// import { createApp } from "vue";
// import MagicBar from "./components/MagicBar.vue";
// const app = createApp({});
// app.component("magic-bar", MagicBar);
// app.mount("#vue");
import { initializeTerminalComponent } from './terminal.js';
['livewire:navigated', 'alpine:init'].forEach((event) => {

View File

@@ -1,682 +0,0 @@
<template>
<Transition name="fade">
<div>
<div class="flex items-center p-1 px-2 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-100"
@click="showCommandPalette = true">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" 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 d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" />
<path d="M21 21l-6 -6" />
</svg>
<span class="flex-1"></span>
<span class="ml-2 kbd-custom">/</span>
</div>
<div class="relative" role="dialog" aria-modal="true" v-if="showCommandPalette" @keyup.esc="resetState">
<div class="fixed inset-0 transition-opacity bg-opacity-90 bg-coolgray-100" @click.self="resetState">
</div>
<div class="fixed inset-0 p-4 mx-auto overflow-y-auto lg:w-[70rem] sm:p-10 md:px-20"
@click.self="resetState">
<div class="overflow-hidden transition-all transform bg-coolgray-200 ring-1 ring-black ring-opacity-5">
<div class="relative">
<svg class="absolute w-5 h-5 text-gray-400 pointer-events-none left-3 top-2.5"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
clip-rule="evenodd" />
</svg>
<input type="text" v-model="search" ref="searchInput" @keydown.down="focusNext(magic.length)"
@keydown.up="focusPrev(magic.length)" @keyup.enter="callAction"
class="w-full h-10 pr-4 rounded outline-none dark:text-white bg-coolgray-400 pl-11 placeholder:text-neutral-700 sm:text-sm focus:outline-none"
placeholder="Search, jump or create... magically... 🪄" role="combobox"
aria-expanded="false" aria-controls="options">
</div>
<ul class="px-4 pb-2 overflow-y-auto max-h-96 scroll-py-10 scroll-pb-2 scrollbar" id="options"
role="listbox">
<li v-if="sequenceState.sequence.length !== 0">
<h2 v-if="sequenceState.sequence[sequenceState.currentActionIndex] && possibleSequences[sequenceState.sequence[sequenceState.currentActionIndex]]"
class="mt-4 mb-2 text-xs font-semibold text-neutral-500">{{
possibleSequences[sequenceState.sequence[sequenceState.currentActionIndex]].newTitle }}
</h2>
<ul class="mt-2 -mx-4 dark:text-white">
<li class="flex items-center px-4 py-2 cursor-pointer select-none group hover:bg-coolgray-400"
id="option-1" role="option" tabindex="-1"
@click="addNew(sequenceState.sequence[sequenceState.currentActionIndex])">
<svg xmlns=" http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 5l0 14" />
<path d="M5 12l14 0" />
</svg>
<span class="flex-auto ml-3 truncate">
<span v-if="search"><span class="capitalize ">{{
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
will be:
<span class="inline-block dark:text-warning">{{ search }}</span>
</span>
<span v-else><span class="capitalize ">{{
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
will be:
<span class="inline-block dark:text-warning">randomly generated (type to
change)</span>
</span>
</span>
</li>
</ul>
</li>
<li>
<ul v-if="magic.length == 0" class="mt-2 -mx-4 dark:text-white">
<li class="flex items-center px-4 py-2 select-none group">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" 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="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M9 10l.01 0" />
<path d="M15 10l.01 0" />
<path d="M9 15l6 0" />
</svg>
<span class="flex-auto ml-3 truncate">Nothing found. Ooops.</span>
</li>
</ul>
<h2 v-if="magic.length !== 0 && sequenceState.sequence[sequenceState.currentActionIndex] && possibleSequences[sequenceState.sequence[sequenceState.currentActionIndex]]"
class="mt-4 mb-2 text-xs font-semibold text-neutral-500">{{
possibleSequences[sequenceState.sequence[sequenceState.currentActionIndex]].title }}
</h2>
<ul v-if="magic.length != 0" class="mt-2 -mx-4 dark:text-white">
<li class="flex items-center px-4 py-2 transition-all cursor-pointer select-none group hover:bg-coolgray-400"
:class="{ 'bg-coollabs': currentFocus === index }" id="option-1" role="option"
tabindex="-1" v-for="action, index in magic" @click="goThroughSequence(index)"
ref="magicItems">
<div class="relative">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<template
v-if="action.icon === 'git' || sequenceState.sequence[sequenceState.currentActionIndex] === 'git'">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M16 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
<path d="M12 8m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
<path d="M12 16m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
<path d="M12 15v-6" />
<path d="M15 11l-2 -2" />
<path d="M11 7l-1.9 -1.9" />
<path
d="M13.446 2.6l7.955 7.954a2.045 2.045 0 0 1 0 2.892l-7.955 7.955a2.045 2.045 0 0 1 -2.892 0l-7.955 -7.955a2.045 2.045 0 0 1 0 -2.892l7.955 -7.955a2.045 2.045 0 0 1 2.892 0z" />
</template>
<template
v-if="action.icon === 'server' || sequenceState.sequence[sequenceState.currentActionIndex] === 'server'">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M3 4m0 3a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3z" />
<path d="M15 20h-9a3 3 0 0 1 -3 -3v-2a3 3 0 0 1 3 -3h12" />
<path d="M7 8v.01" />
<path d="M7 16v.01" />
<path d="M20 15l-2 3h3l-2 3" />
</template>
<template
v-if="action.icon === 'destination' || sequenceState.sequence[sequenceState.currentActionIndex] === 'destination'">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z" />
<path d="M5 10h3v3h-3z" />
<path d="M8 10h3v3h-3z" />
<path d="M11 10h3v3h-3z" />
<path d="M8 7h3v3h-3z" />
<path d="M11 7h3v3h-3z" />
<path d="M11 4h3v3h-3z" />
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
<path d="M10 16l0 .01" />
</template>
<template
v-if="action.icon === 'storage' || sequenceState.sequence[sequenceState.currentActionIndex] === 'storage'">
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2">
<path d="M4 6a8 3 0 1 0 16 0A8 3 0 1 0 4 6" />
<path d="M4 6v6a8 3 0 0 0 16 0V6" />
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
</g>
</template>
<template
v-if="action.icon === 'project' || sequenceState.sequence[sequenceState.currentActionIndex] === 'project'">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M9 4h3l2 2h5a2 2 0 0 1 2 2v7a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-9a2 2 0 0 1 2 -2" />
<path
d="M17 17v2a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-9a2 2 0 0 1 2 -2h2" />
</template>
<template
v-if="action.icon === 'environment' || sequenceState.sequence[sequenceState.currentActionIndex] === 'environment'">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M16 5l3 3l-2 1l4 4l-3 1l4 4h-9" />
<path d="M15 21l0 -3" />
<path d="M8 13l-2 -2" />
<path d="M8 12l2 -2" />
<path d="M8 21v-13" />
<path
d="M5.824 16a3 3 0 0 1 -2.743 -3.69a3 3 0 0 1 .304 -4.833a3 3 0 0 1 4.615 -3.707a3 3 0 0 1 4.614 3.707a3 3 0 0 1 .305 4.833a3 3 0 0 1 -2.919 3.695h-4z" />
</template>
<template
v-if="action.icon === 'key' || sequenceState.sequence[sequenceState.currentActionIndex] === 'key'">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M14 10m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
<path d="M21 12a9 9 0 1 1 -18 0a9 9 0 0 1 18 0z" />
<path d="M12.5 11.5l-4 4l1.5 1.5" />
<path d="M12 15l-1.5 -1.5" />
</template>
<template
v-if="action.icon === 'goto' || sequenceState.sequence[sequenceState.currentActionIndex] === 'goto'">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 18h4" />
<path
d="M3 8a9 9 0 0 1 9 9v1l1.428 -4.285a12 12 0 0 1 6.018 -6.938l.554 -.277" />
<path d="M15 6h5v5" />
</template>
<template
v-if="action.icon === 'team' || sequenceState.sequence[sequenceState.currentActionIndex] === 'team'">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" />
<path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M17 10h2a2 2 0 0 1 2 2v1" />
<path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M3 13v-1a2 2 0 0 1 2 -2h2" />
</template>
</svg>
<div v-if="action.new"
class="absolute top-0 right-0 -mt-2 -mr-2 font-bold dark:text-warning">+
</div>
</div>
<span class="flex-auto ml-3 truncate">{{ action.name }}</span>
</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div>
</Transition>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import axios from "axios";
const currentFocus = ref(0)
const magicItems = ref()
function focusNext(length) {
if (currentFocus.value === undefined) {
currentFocus.value = 0
} else {
if (length > currentFocus.value + 1) {
currentFocus.value = currentFocus.value + 1
}
}
if (currentFocus.value > 4) {
magicItems.value[currentFocus.value].scrollIntoView({ block: "center", inline: "center", behavior: 'auto' })
}
}
function focusPrev(length) {
if (currentFocus.value === undefined) {
currentFocus.value = length - 1
} else {
if (currentFocus.value > 0) {
currentFocus.value = currentFocus.value - 1
}
}
if (currentFocus.value < length - 4) {
magicItems.value[currentFocus.value].scrollIntoView({ block: "center", inline: "center", behavior: 'auto' })
}
}
async function callAction() {
await goThroughSequence(currentFocus.value)
}
const showCommandPalette = ref(false)
const search = ref()
const searchInput = ref()
const baseUrl = '/magic'
const uuidSelector = ['project', 'destination']
const nameSelector = ['environment']
const possibleSequences = {
server: {
newTitle: 'Create a new Server',
title: 'Select a server'
},
destination: {
newTitle: 'Create a new Destination',
title: 'Select a destination'
},
project: {
newTitle: 'Create a new Project',
title: 'Select a project'
},
environment: {
newTitle: 'Create a new Environment',
title: 'Select an environment'
},
}
const magicActions = [{
id: 1,
name: 'Deploy: Public Repository',
tags: 'git,github,public',
icon: 'git',
new: true,
sequence: ['main', 'server', 'destination', 'project', 'environment', 'redirect']
},
{
id: 2,
name: 'Deploy: Private Repository (with GitHub Apps)',
tags: 'git,github,private',
icon: 'git',
new: true,
sequence: ['main', 'server', 'destination', 'project', 'environment', 'redirect']
},
{
id: 3,
name: 'Deploy: Private Repository (with Deploy Key)',
tags: 'git,github,private,deploy,key',
icon: 'git',
new: true,
sequence: ['main', 'server', 'destination', 'project', 'environment', 'redirect']
},
{
id: 4,
name: 'Deploy: Dockerfile',
tags: 'dockerfile,deploy',
icon: 'destination',
new: true,
sequence: ['main', 'server', 'destination', 'project', 'environment', 'redirect']
},
{
id: 5,
name: 'Create: Server',
tags: 'server,ssh,new,create',
icon: 'server',
new: true,
sequence: ['main', 'redirect']
},
{
id: 6,
name: 'Create: Source',
tags: 'source,git,gitlab,github,bitbucket,gitea,new,create',
icon: 'git',
new: true,
sequence: ['main', 'redirect']
},
{
id: 7,
name: 'Create: Private Key',
tags: 'private,key,ssh,new,create',
icon: 'key',
new: true,
sequence: ['main', 'redirect']
},
{
id: 8,
name: 'Create: Destination',
tags: 'destination,docker,network,new,create',
icon: 'destination',
new: true,
sequence: ['main', 'server', 'redirect']
},
{
id: 9,
name: 'Create: Team',
tags: 'team,member,new,create',
icon: 'team',
new: true,
sequence: ['main', 'redirect']
},
{
id: 10,
name: 'Create: S3 Storage',
tags: 's3,storage,new,create',
icon: 'storage',
new: true,
sequence: ['main', 'redirect']
},
{
id: 11,
name: 'Goto: S3 Storage',
tags: 's3,storage',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 12,
name: 'Goto: Dashboard',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 13,
name: 'Goto: Servers',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 14,
name: 'Goto: Private Keys',
tags: 'destination,docker,network,new,create,ssh,private,key',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 15,
name: 'Goto: Projects',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 16,
name: 'Goto: Sources',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 17,
name: 'Goto: Destinations',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 18,
name: 'Goto: Settings',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 19,
name: 'Goto: Terminal',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 20,
name: 'Goto: Notifications',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 21,
name: 'Goto: Profile',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 22,
name: 'Goto: Teams',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 23,
name: 'Goto: Switch Teams',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 24,
name: 'Goto: Onboarding process',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 25,
name: 'Goto: API Tokens',
tags: 'api,tokens,rest',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 26,
name: 'Goto: Team Shared Variables',
tags: 'team,shared,variables',
icon: 'goto',
sequence: ['main', 'redirect']
}
]
const initialState = {
sequence: [],
currentActionIndex: 0,
magicActions,
selected: {}
}
const sequenceState = ref({ ...initialState })
function focusSearch(event) {
if (event.target.nodeName === 'BODY') {
if (event.key === '/') {
event.preventDefault();
showCommandPalette.value = true;
}
}
}
onMounted(() => {
window.addEventListener("keydown", focusSearch);
})
onUnmounted(() => {
window.removeEventListener("keydown", focusSearch);
})
watch(showCommandPalette, async (value) => {
if (value) {
await nextTick();
searchInput.value.focus();
}
})
watch(search, async () => {
currentFocus.value = 0
})
const magic = computed(() => {
if (search.value) {
return sequenceState.value.magicActions.filter(action => {
return action.name.toLowerCase().includes(search.value.toLowerCase()) || action.tags?.toLowerCase().includes(search.value.toLowerCase())
})
}
return sequenceState.value.magicActions
})
async function addNew(name) {
let targetUrl = new URL(window.location.origin)
let newUrl = new URL(`${window.location.origin}${baseUrl}/${name}/new`);
if (search.value) {
targetUrl.searchParams.append('name', search.value)
newUrl.searchParams.append('name', search.value)
}
switch (name) {
case 'server':
targetUrl.pathname = '/server/new'
window.location.href = targetUrl.href
break;
case 'destination':
targetUrl.pathname = '/destination/new'
window.location.href = targetUrl.href
break;
case 'project':
const { data: { project_uuid } } = await axios(newUrl.href)
search.value = ''
sequenceState.value.selected['project'] = project_uuid
sequenceState.value.magicActions = await getEnvironments(project_uuid)
sequenceState.value.currentActionIndex += 1
break;
case 'environment':
newUrl.searchParams.append('project_uuid', sequenceState.value.selected.project)
const { data: { environment_name } } = await axios(newUrl.href)
search.value = ''
sequenceState.value.selected['environment'] = environment_name
redirect()
break;
}
}
function resetState() {
showCommandPalette.value = false
sequenceState.value = { ...initialState }
search.value = ''
}
async function goThroughSequence(actionId) {
let currentSequence = null;
let nextSequence = null;
if (sequenceState.value.selected.main === undefined) {
const { sequence, id } = magic.value[actionId];
currentSequence = sequence[sequenceState.value.currentActionIndex]
nextSequence = sequence[sequenceState.value.currentActionIndex + 1]
sequenceState.value.sequence = sequence
sequenceState.value.selected = {
main: id
}
} else {
currentSequence = sequenceState.value.sequence[sequenceState.value.currentActionIndex]
nextSequence = sequenceState.value.sequence[sequenceState.value.currentActionIndex + 1]
let selectedId = sequenceState.value.magicActions[actionId].id
if (uuidSelector.includes(currentSequence)) {
selectedId = sequenceState.value.magicActions[actionId].uuid
}
if (nameSelector.includes(currentSequence)) {
selectedId = sequenceState.value.magicActions[actionId].name
}
sequenceState.value.selected = {
...sequenceState.value.selected,
[currentSequence]: selectedId
}
}
switch (nextSequence) {
case 'server':
sequenceState.value.magicActions = await getServers();
break;
case 'destination':
sequenceState.value.magicActions = await getDestinations(sequenceState.value.selected[currentSequence]);
break;
case 'project':
sequenceState.value.magicActions = await getProjects()
break;
case 'environment':
sequenceState.value.magicActions = await getEnvironments(sequenceState.value.selected[currentSequence])
break;
case 'redirect':
redirect()
break;
default:
break;
}
sequenceState.value.currentActionIndex += 1
search.value = ''
searchInput.value.focus()
currentFocus.value = 0
}
async function getServers() {
const { data: { servers } } = await axios.get(`${baseUrl}/servers`);
return servers;
}
async function getDestinations(serverId) {
const { data: { destinations } } = await axios.get(`${baseUrl}/destinations?server_id=${serverId}`);
return destinations;
}
async function getProjects() {
const { data: { projects } } = await axios.get(`${baseUrl}/projects`);
return projects;
}
async function getEnvironments(project_uuid) {
const { data: { environments } } = await axios.get(`${baseUrl}/environments?project_uuid=${project_uuid}`);
return environments;
}
async function redirect() {
let targetUrl = new URL(window.location.origin)
const selected = sequenceState.value.selected
const { main, destination = null, project = null, environment = null, server = null } = selected
switch (main) {
case 1:
targetUrl.pathname = `/project/${project}/${environment}/new`
targetUrl.searchParams.append('type', 'public')
targetUrl.searchParams.append('destination', destination)
break;
case 2:
targetUrl.pathname = `/project/${project}/${environment}/new`
targetUrl.searchParams.append('type', 'private-gh-app')
targetUrl.searchParams.append('destination', destination)
break;
case 3:
targetUrl.pathname = `/project/${project}/${environment}/new`
targetUrl.searchParams.append('type', 'private-deploy-key')
targetUrl.searchParams.append('destination', destination)
break;
case 4:
targetUrl.pathname = `/project/${project}/${environment}/new`
targetUrl.searchParams.append('type', 'dockerfile')
targetUrl.searchParams.append('destination', destination)
break;
case 5:
targetUrl.pathname = `/server/new`
break;
case 6:
targetUrl.pathname = `/source/new`
break;
case 7:
targetUrl.pathname = `/security/private-key/new`
break;
case 8:
targetUrl.pathname = `/destination/new`
targetUrl.searchParams.append('server', server)
break;
case 9:
targetUrl.pathname = `/team/new`
break;
case 10:
targetUrl.pathname = `/team/storages/new`
break;
case 11:
targetUrl.pathname = `/team/storages/`
break;
case 12:
targetUrl.pathname = `/`
break;
case 13:
targetUrl.pathname = `/servers`
break;
case 14:
targetUrl.pathname = `/security/private-key`
break;
case 15:
targetUrl.pathname = `/projects`
break;
case 16:
targetUrl.pathname = `/sources`
break;
case 17:
targetUrl.pathname = `/destinations`
break;
case 18:
targetUrl.pathname = `/settings`
break;
case 19:
targetUrl.pathname = `/terminal`
break;
case 20:
targetUrl.pathname = `/team/notifications`
break;
case 21:
targetUrl.pathname = `/profile`
break;
case 22:
targetUrl.pathname = `/team`
break;
case 23:
targetUrl.pathname = `/team`
break;
case 24:
targetUrl.pathname = `/onboarding`
break;
case 25:
targetUrl.pathname = `/security/api-tokens`
break;
case 26:
targetUrl.pathname = `/team/shared-variables`
break;
}
window.location.href = targetUrl;
}
</script>

View File

@@ -0,0 +1,18 @@
@props(['text'])
<div class="relative" x-data="{ copied: false, isSecure: window.isSecureContext }">
<input type="text" value="{{ $text }}" readonly class="input">
<button
x-show="isSecure"
@click.prevent="copied = true; navigator.clipboard.writeText({{ Js::from($text) }}); setTimeout(() => copied = false, 1000)"
class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 text-gray-400 hover:text-gray-300 transition-colors"
title="Copy to clipboard">
<svg x-show="!copied" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
<svg x-show="copied" class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
</button>
</div>

View File

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

View File

@@ -21,6 +21,7 @@
'dispatchEvent' => false,
'dispatchEventType' => 'success',
'dispatchEventMessage' => '',
'ignoreWire' => true,
])
@php
@@ -28,7 +29,7 @@
$disableTwoStepConfirmation = data_get(InstanceSettings::get(), 'disable_two_step_confirmation');
@endphp
<div wire:ignore x-data="{
<div {{ $ignoreWire ? 'wire:ignore' : '' }} x-data="{
modalOpen: false,
step: {{ empty($checkboxes) ? 2 : 1 }},
initialStep: {{ empty($checkboxes) ? 2 : 1 }},
@@ -40,7 +41,6 @@
userConfirmationText: '',
confirmWithText: @js($confirmWithText && !$disableTwoStepConfirmation),
confirmWithPassword: @js($confirmWithPassword && !$disableTwoStepConfirmation),
copied: false,
submitAction: @js($submitAction),
passwordError: '',
selectedActions: @js(collect($checkboxes)->pluck('id')->filter(fn($id) => $this->$id)->values()->all()),
@@ -91,13 +91,6 @@
}
});
},
copyConfirmationText() {
navigator.clipboard.writeText(this.confirmationText);
this.copied = true;
setTimeout(() => {
this.copied = false;
}, 2000);
},
toggleAction(id) {
const index = this.selectedActions.indexOf(id);
if (index > -1) {
@@ -106,8 +99,9 @@
this.selectedActions.push(id);
}
}
}" @keydown.escape.window="modalOpen = false; resetModal()"
:class="{ 'z-40': modalOpen }" class="relative w-auto h-auto">
}"
@keydown.escape.window="modalOpen = false; resetModal()" :class="{ 'z-40': modalOpen }"
class="relative w-auto h-auto">
@if ($customButton)
@if ($buttonFullWidth)
<x-forms.button @click="modalOpen=true" class="w-full">
@@ -255,29 +249,7 @@
<h4 class="mb-2 text-lg font-semibold">Confirm Actions</h4>
<p class="mb-2 text-sm">{{ $confirmationLabel }}</p>
<div class="relative mb-2">
<input type="text" x-model="confirmationText"
class="p-2 pr-10 w-full text-black rounded cursor-text input" readonly>
<button @click="copyConfirmationText()"
x-show="window.isSecureContext"
class="absolute right-2 top-1/2 text-gray-500 transform -translate-y-1/2 hover:text-gray-700"
title="Copy confirmation text" x-ref="copyButton">
<template x-if="!copied">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5"
viewBox="0 0 20 20" fill="currentColor">
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
<path
d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
</svg>
</template>
<template x-if="copied">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-green-500"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</template>
</button>
<x-forms.copy-button text="{{ $confirmationText }}" />
</div>
<label for="userConfirmationText"

View File

@@ -1,6 +1,11 @@
<div class="flex flex-col items-start gap-2 min-w-fit">
<a wire:navigate class="menu-item {{ $activeMenu === 'general' ? 'menu-item-active' : '' }}"
href="{{ route('server.show', ['server_uuid' => $server->uuid]) }}">General</a>
@if ($server->isFunctional())
<a wire:navigate class="menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}"
href="{{ route('server.advanced', ['server_uuid' => $server->uuid]) }}">Advanced
</a>
@endif
<a wire:navigate class="menu-item {{ $activeMenu === 'private-key' ? 'menu-item-active' : '' }}"
href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}">Private Key
</a>
@@ -16,9 +21,6 @@
<a wire:navigate class="menu-item {{ $activeMenu === 'destinations' ? 'menu-item-active' : '' }}"
href="{{ route('server.destinations', ['server_uuid' => $server->uuid]) }}">Destinations
</a>
<a wire:navigate class="menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}"
href="{{ route('server.advanced', ['server_uuid' => $server->uuid]) }}">Advanced
</a>
<a wire:navigate class="menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}"
href="{{ route('server.log-drains', ['server_uuid' => $server->uuid]) }}">Log
Drains</a>

View File

@@ -0,0 +1,28 @@
<x-emails.layout>
<h2>SSL Certificates Renewed</h2>
<p>SSL certificates have been renewed for the following resources:</p>
<ul>
@foreach($resources as $resource)
<li>{{ $resource->name }}</li>
@endforeach
</ul>
<div style="margin: 20px 0; padding: 15px; background-color: #fff3cd; border: 1px solid #ffeeba; border-radius: 4px;">
<strong>⚠️ Action Required:</strong> These resources need to be redeployed manually for the new SSL certificates to take effect. Please do this in the next few days to ensure your database connections remain accessible.
</div>
<p>The old SSL certificates will remain valid for approximately 14 more days, as we renew certificates 14 days before their expiration.</p>
@if(isset($urls) && count($urls) > 0)
<div style="margin-top: 20px;">
<p>You can redeploy these resources here:</p>
<ul>
@foreach($urls as $name => $url)
<li><a href="{{ $url }}">{{ $name }}</a></li>
@endforeach
</ul>
</div>
@endif
</x-emails.layout>

View File

@@ -37,7 +37,7 @@
@env('local')
<link rel="icon" href="{{ asset('coolify-logo-dev-transparent.png') }}" type="image/x-icon" />
@else
<link rel="icon" href="{{ asset('coolify-transparent.png') }}" type="image/x-icon" />
<link rel="icon" href="{{ asset('coolify-logo.svg') }}" type="image/x-icon" />
@endenv
<meta name="csrf-token" content="{{ csrf_token() }}">
@vite(['resources/js/app.js', 'resources/css/app.css'])

View File

@@ -20,12 +20,15 @@
</x-forms.button>
@endif
</div>
<div class="w-32">
<div class="w-48">
<x-forms.checkbox instantSave="instantSaveDiscordEnabled" id="discordEnabled" label="Enabled" />
<x-forms.checkbox instantSave="instantSaveDiscordPingEnabled" id="discordPingEnabled"
helper="If enabled, a ping (@here) will be sent to the notification when a critical event happens."
label="Ping Enabled" />
</div>
<x-forms.input type="password"
helper="Create a Discord Server and generate a Webhook URL. <br><a class='inline-block underline dark:text-white' href='https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks' target='_blank'>Webhook Documentation</a>" required
id="discordWebhookUrl" label="Webhook" />
helper="Create a Discord Server and generate a Webhook URL. <br><a class='inline-block underline dark:text-white' href='https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks' target='_blank'>Webhook Documentation</a>"
required id="discordWebhookUrl" label="Webhook" />
</form>
<h2 class="mt-4">Notification Settings</h2>
<p class="mb-4">

View File

@@ -14,7 +14,7 @@
<x-modal-input buttonTitle="Send Test Email" title="Send Test Email">
<form wire:submit.prevent="sendTestEmail" class="flex flex-col w-full gap-2">
<x-forms.input wire:model="testEmailAddress" placeholder="test@example.com"
id="testEmailAddress" label="Recipients" required />
id="testEmailAddress" label="Recipient" required />
<x-forms.button type="submit" @click="modalOpen=false">
Send Email
</x-forms.button>

View File

@@ -19,6 +19,7 @@
<h2>Change Password</h2>
<x-forms.button type="submit" label="Save">Save</x-forms.button>
</div>
<div class="text-xs font-bold text-warning pb-2">Resetting the password will logout all sessions.</div>
<div class="flex flex-col gap-2">
<x-forms.input id="current_password" label="Current Password" required type="password" />
<div class="flex gap-2">
@@ -36,61 +37,21 @@
<div class="flex flex-col gap-4">
<form action="/user/confirmed-two-factor-authentication" method="POST" class="flex items-end gap-2">
@csrf
<x-forms.input type="text" inputmode="numeric" pattern="[0-9]*" id="code" label="One time (OTP) code" required />
<x-forms.input type="text" inputmode="numeric" pattern="[0-9]*" id="code"
label="One time (OTP) code" required />
<x-forms.button type="submit">Validate 2FA</x-forms.button>
</form>
<div class="flex flex-col items-start">
<div class="flex items-center justify-center w-80 h-80 bg-white p-4 border-4 border-gray-300 rounded-lg shadow-lg">
<div
class="flex items-center justify-center w-80 h-80 bg-white p-4 border-4 border-gray-300 rounded-lg shadow-lg">
{!! request()->user()->twoFactorQrCodeSvg() !!}
</div>
<div x-data="{
showCode: false,
secretKey: '{{ decrypt(request()->user()->two_factor_secret) }}',
otpUrl: '{{ request()->user()->twoFactorQrCodeUrl() }}',
copiedSecretKey: false,
copiedOtpUrl: false
}" class="py-4 w-full">
<div class="flex flex-col gap-2" x-show="showCode">
<div class="relative">
<x-forms.input
x-model="secretKey"
label="Secret Key"
readonly
class="font-mono pr-10"
/>
<button
x-show="window.isSecureContext"
@click="navigator.clipboard.writeText(secretKey); copiedSecretKey = true; setTimeout(() => copiedSecretKey = false, 2000)"
class="absolute right-2 bottom-1 p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
>
<svg x-show="!copiedSecretKey" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" />
</svg>
<svg x-show="copiedSecretKey" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-green-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
</button>
</div>
<div class="relative" >
<x-forms.input
x-model="otpUrl"
label="OTP URL"
readonly
class="font-mono pr-10"
/>
<button
x-show="window.isSecureContext"
@click="navigator.clipboard.writeText(otpUrl); copiedOtpUrl = true; setTimeout(() => copiedOtpUrl = false, 2000)"
class="absolute right-2 bottom-1 p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
>
<svg x-show="!copiedOtpUrl" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" />
</svg>
<svg x-show="copiedOtpUrl" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-green-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
</button>
</div>
<div class="flex flex-col gap-2 pb-2" x-show="showCode">
<x-forms.copy-button text="{{ decrypt(request()->user()->two_factor_secret) }}" />
<x-forms.copy-button text="{{ request()->user()->twoFactorQrCodeUrl() }}" />
</div>
<x-forms.button x-on:click="showCode = !showCode" class="mt-2">
<span x-text="showCode ? 'Hide Secret Key and OTP URL' : 'Show Secret Key and OTP URL'"></span>

View File

@@ -15,8 +15,7 @@
href="{{ route('project.application.advanced', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"
wire:navigate>Advanced</a>
@if ($application->destination->server->isSwarm())
<a class="menu-item"
wire:current.exact="menu-item-active"
<a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"
wire:navigate>Swarm Configuration</a>
@endif
@@ -60,21 +59,22 @@
<a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.application.preview-deployments', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"
wire:navigate>Preview Deployments</a>
<a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.application.healthcheck', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"
wire:navigate>Healthcheck</a>
@if ($application->build_pack !== 'dockercompose')
<a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.application.healthcheck', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"
wire:navigate>Healthcheck</a>
@endif
<a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.application.rollback', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"
wire:navigate>Rollback</a>
<a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.application.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"
wire:navigate>Resource Limits</a>
<a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.application.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"
wire:navigate>Resource Operations</a>
<a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.application.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" >Metrics</a>
href="{{ route('project.application.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Metrics</a>
<a class="menu-item" wire:current.exact="menu-item-active"
href="{{ route('project.application.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}"
wire:navigate>Tags</a>
@@ -103,7 +103,7 @@
<livewire:project.shared.webhooks :resource="$application" />
@elseif ($currentRoute === 'project.application.preview-deployments')
<livewire:project.application.previews :application="$application" />
@elseif ($currentRoute === 'project.application.healthcheck')
@elseif ($currentRoute === 'project.application.healthcheck' && $application->build_pack !== 'dockercompose')
<livewire:project.shared.health-checks :resource="$application" />
@elseif ($currentRoute === 'project.application.rollback')
<livewire:project.application.rollback :application="$application" />

View File

@@ -69,6 +69,17 @@
<x-forms.button wire:click="generateNginxConfiguration">Generate Default Nginx
Configuration</x-forms.button>
@endif
<div class="w-96 pb-8">
@if ($application->could_set_build_commands())
<x-forms.checkbox instantSave id="application.settings.is_static" label="Is it a static site?"
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
@endif
@if ($application->settings->is_static && $application->build_pack !== 'static')
<x-forms.checkbox label="Is it a SPA (Single Page Application)?"
helper="If your application is a SPA, enable this." id="application.settings.is_spa"
instantSave></x-forms.checkbox>
@endif
</div>
@if ($application->build_pack !== 'dockercompose')
<div class="flex items-end gap-2">
@if ($application->settings->is_container_label_readonly_enabled == false)
@@ -185,7 +196,7 @@
</div>
<div class="pt-1 text-xs">Nixpacks will detect the required configuration
automatically.
<a class="underline" href="https://coolify.io/docs/applications">Framework
<a class="underline" href="https://coolify.io/docs/applications/">Framework
Specific Docs</a>
</div>
@endif
@@ -274,13 +285,6 @@
label="Use a Build Server?" />
</div>
@endif
@if ($application->could_set_build_commands())
<div class="w-96">
<x-forms.checkbox instantSave id="application.settings.is_static"
label="Is it a static site?"
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
</div>
@endif
@endif
</div>
@endif
@@ -310,7 +314,7 @@
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="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
{{-- <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 autogenrate 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 autogenrate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
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="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox> --}}
</div>
@endif
@@ -354,7 +358,7 @@
@endif
<div class="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 autogenrate 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 autogenrate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
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="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
<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."

View File

@@ -49,6 +49,45 @@
readonly value="Starting the database will generate this." />
@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" />
@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." />
@endif
</div>
</div>
</div>
<div>
<div class="flex flex-col py-2 w-64">
<div class="flex items-center gap-2 pb-2">

View File

@@ -49,6 +49,45 @@
readonly value="Starting the database will generate this." />
@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" />
@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." />
@endif
</div>
</div>
</div>
<div>
<div class="flex flex-col py-2 w-64">
<div class="flex items-center gap-2 pb-2">

View File

@@ -65,6 +65,49 @@
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 ($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>
<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="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." />
@endif
</div>
</div>
</div>
<div>
<div class="flex flex-col py-2 w-64">
<div class="flex items-center gap-2 pb-2">

View File

@@ -55,35 +55,101 @@
type="password" readonly wire:model="db_url_public" />
@endif
</div>
<div>
<div class="flex flex-col py-2 w-64">
<div class="flex items-center gap-2 pb-2">
<div class="flex items-center">
<h3>Proxy</h3>
<x-loading wire:loading wire:target="instantSave" />
</div>
@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" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}"
@click="slideOverOpen=true">Logs</x-forms.button>
</x-slide-over>
<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)
<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>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
</div>
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
</div>
<x-forms.textarea label="Custom MongoDB Configuration" rows="10" id="database.mongo_conf" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" />
@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>
<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="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." />
@endif
</div>
@if ($database->enable_ssl)
<div class="mx-2">
@if (str($database->status)->contains('exited'))
<x-forms.select id="database.ssl_mode" label="SSL Mode" wire:model.live="database.ssl_mode"
instantSave="instantSaveSSL"
helper="Choose the SSL verification mode for MongoDB connections">
<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="database.ssl_mode" 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-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">
<div class="flex items-center">
<h3>Proxy</h3>
<x-loading wire:loading wire:target="instantSave" />
</div>
@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" 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>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
</div>
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
</div>
<x-forms.textarea label="Custom MongoDB Configuration" rows="10" id="database.mongo_conf" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" />
</div>
</form>
</div>

View File

@@ -65,6 +65,73 @@
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 ($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>
<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="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." />
@endif
</div>
@if ($database->enable_ssl)
<div class="mx-2">
@if (str($database->status)->contains('exited'))
<x-forms.select id="database.ssl_mode" label="SSL Mode" wire:model.live="database.ssl_mode"
instantSave="instantSaveSSL"
helper="Choose the SSL verification mode for MySQL connections">
<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="database.ssl_mode" 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>
<div>
<div class="flex flex-col py-2 w-64">
<div class="flex items-center gap-2 pb-2">

View File

@@ -73,58 +73,132 @@
type="password" readonly wire:model="db_url_public" />
@endif
</div>
<div>
<div class="flex flex-col py-2 w-64">
<div class="flex items-center gap-2 pb-2">
<div class="flex items-center">
<h3>Proxy</h3>
<x-loading wire:loading wire:target="instantSave" />
</div>
@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" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}"
@click="slideOverOpen=true">Logs</x-forms.button>
</x-slide-over>
<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>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
</div>
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
</div>
<x-forms.textarea label="Custom PostgreSQL Configuration" rows="10" id="database.postgres_conf" />
</form>
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" />
</div>
<div class="pb-16">
<div class="flex gap-2 pt-4 pb-2">
<h3>Initialization scripts</h3>
<x-modal-input buttonTitle="+ Add" title="New Init Script">
<form class="flex flex-col w-full gap-2 rounded" wire:submit='save_new_init_script'>
<x-forms.input placeholder="create_test_db.sql" id="new_filename" label="Filename" required />
<x-forms.textarea rows="20" placeholder="CREATE DATABASE test;" id="new_content"
label="Content" required />
<x-forms.button type="submit">
Save
</x-forms.button>
</form>
</x-modal-input>
@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>
<div class="flex flex-col gap-2">
@forelse(data_get($database,'init_scripts', []) as $script)
<livewire:project.database.init-script :script="$script" :wire:key="$script['index']" />
@empty
<div>No initialization scripts found.</div>
@endforelse
<div class="flex flex-col gap-2">
<div class="w-64" wire:key='enable_ssl'>
@if ($database->isExited())
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." />
@endif
</div>
@if ($database->enable_ssl)
<div class="mx-2">
@if ($database->isExited())
<x-forms.select id="database.ssl_mode" label="SSL Mode"
wire:model.live="database.ssl_mode" instantSave="instantSaveSSL"
helper="Choose the SSL verification mode for PostgreSQL connections">
<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="database.ssl_mode" 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 justify-between py-2">
<div class="flex items-center gap-2">
<h3>Proxy</h3>
<x-loading wire:loading wire:target="instantSave" />
</div>
@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" 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">
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
</div>
</div>
<div class="flex flex-col gap-2">
<x-forms.textarea label="Custom PostgreSQL Configuration" rows="10"
id="database.postgres_conf" />
</div>
</form>
<div class="flex flex-col gap-4 pt-4">
<h3>Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" />
</div>
<div class="pb-16">
<div class="flex items-center gap-2 pb-2">
<h3>Initialization scripts</h3>
<x-modal-input buttonTitle="+ Add" title="New Init Script">
<form class="flex flex-col w-full gap-2 rounded" wire:submit='save_new_init_script'>
<x-forms.input placeholder="create_test_db.sql" id="new_filename" label="Filename"
required />
<x-forms.textarea rows="20" placeholder="CREATE DATABASE test;" id="new_content"
label="Content" required />
<x-forms.button type="submit">
Save
</x-forms.button>
</form>
</x-modal-input>
</div>
<div class="flex flex-col gap-2">
@forelse(data_get($database,'init_scripts', []) as $script)
<livewire:project.database.init-script :script="$script" :wire:key="$script['index']" />
@empty
<div>No initialization scripts found.</div>
@endforelse
</div>
</div>
</div>
</div>

View File

@@ -49,6 +49,45 @@
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 ($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" wire:key='enable_ssl'>
@if (str($database->status)->contains('exited'))
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." />
@endif
</div>
</div>
</div>
<div>
<div class="flex flex-col py-2 w-64">
<div class="flex items-center gap-2 pb-2">

View File

@@ -103,7 +103,7 @@
:src='service.logo'
x-on:error.window="$event.target.src = service.logo_github_url"
onerror="this.onerror=null; this.src=this.getAttribute('data-fallback');"
x-on:error="$event.target.src = '/svgs/coolify-transparent.png'"
x-on:error="$event.target.src = 'coolify-logo.svg'"
:data-fallback='service.logo_github_url' />
</template>
</x-slot:logo>

View File

@@ -1,35 +1,30 @@
<div class="py-4 ">
<div class="">
<div class="flex flex-col justify-center pb-4 text-sm select-text">
{{-- @if (data_get($resource, 'build_pack') === 'dockercompose')
<h4>{{ data_get($resource, 'name', 'unknown') }}</h4>
@endif --}}
@if ($fileStorage->is_directory)
<h4 class="dark:text-white pt-4 border-t dark:border-coolgray-200">Directory Mount</h4>
@else
<h4 class="dark:text-white pt-4 border-t dark:border-coolgray-200">File Mount</h4>
@endif
<x-forms.input label="Source Path" :value="$fileStorage->fs_path" readonly />
<x-forms.input label="Destination Path" :value="$fileStorage->mount_path" readonly />
<div class="flex gap-2 md:flex-row flex-col pt-4">
<x-forms.input label="Source Path" :value="$fileStorage->fs_path" readonly />
<x-forms.input label="Destination Path" :value="$fileStorage->mount_path" readonly />
</div>
</div>
<form wire:submit='submit' class="flex flex-col gap-2">
<div class="flex gap-2">
@if ($fileStorage->is_directory)
<x-modal-confirmation title="Confirm Directory Conversion to File?" buttonTitle="Convert to file"
submitAction="convertToFile" :actions="[
<x-modal-confirmation :ignoreWire="false" title="Confirm Directory Conversion to File?"
buttonTitle="Convert to file" submitAction="convertToFile" :actions="[
'All files in this directory will be permanently deleted and an empty file will be created in its place.',
]" confirmationText="{{ $fs_path }}"
]"
confirmationText="{{ $fs_path }}"
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
shortConfirmationLabel="Filepath" :confirmWithPassword="false" step2ButtonText="Convert to file" />
<x-modal-confirmation title="Confirm Directory Deletion?" buttonTitle="Delete Directory" isErrorButton
submitAction="delete" :checkboxes="$directoryDeletionCheckboxes" :actions="[
<x-modal-confirmation :ignoreWire="false" title="Confirm Directory Deletion?" buttonTitle="Delete"
isErrorButton submitAction="delete" :checkboxes="$directoryDeletionCheckboxes" :actions="[
'The selected directory and all its contents will be permanently deleted from the container.',
]" confirmationText="{{ $fs_path }}"
]"
confirmationText="{{ $fs_path }}"
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
shortConfirmationLabel="Filepath" step3ButtonText="Permanently Delete" />
@else
@if (!$fileStorage->is_binary)
<x-modal-confirmation title="Confirm File Conversion to Directory?"
<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.',
]"
@@ -37,27 +32,13 @@
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
shortConfirmationLabel="Filepath" :confirmWithPassword="false" step2ButtonText="Convert to directory" />
@endif
<x-modal-confirmation title="Confirm File Deletion?" buttonTitle="Delete File" isErrorButton
submitAction="delete" :checkboxes="$fileDeletionCheckboxes" :actions="['The selected file will be permanently deleted from the container.']" confirmationText="{{ $fs_path }}"
<x-forms.button type="button" wire:click="loadStorageOnServer">Load from server</x-forms.button>
<x-modal-confirmation :ignoreWire="false" title="Confirm File Deletion?" buttonTitle="Delete"
isErrorButton submitAction="delete" :checkboxes="$fileDeletionCheckboxes" :actions="['The selected file will be permanently deleted from the container.']"
confirmationText="{{ $fs_path }}"
confirmationLabel="Please confirm the execution of the actions by entering the Filepath below"
shortConfirmationLabel="Filepath" step3ButtonText="Permanently Delete" />
@endif
{{-- @if (!$fileStorage->is_based_on_git)
<x-modal-confirmation isErrorButton buttonTitle="Delete">
<div class="px-2">This storage will be deleted. It is not reversible. <strong
class="text-error">Please
think
again.</strong><br><br></div>
<h4>Actions</h4>
@if ($fileStorage->is_directory)
<x-forms.checkbox id="permanently_delete"
label="Permanently delete directory from the server?"></x-forms.checkbox>
@else
<x-forms.checkbox id="permanently_delete"
label="Permanently delete file from the server?"></x-forms.checkbox>
@endif
</x-modal-confirmation>
@endif --}}
</div>
@if (!$fileStorage->is_directory)
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))

View File

@@ -37,9 +37,8 @@
@endif
@if ($fileStorage->count() > 0)
<div class="flex flex-col gap-2">
@foreach ($fileStorage->sort() as $fileStorage)
<livewire:project.service.file-storage :fileStorage="$fileStorage"
wire:key="resource-{{ $fileStorage->uuid }}" />
@foreach ($fileStorage as $fs)
<livewire:project.service.file-storage :fileStorage="$fs" wire:key="resource-{{ $fs->uuid }}" />
@endforeach
</div>
@endif

View File

@@ -31,46 +31,48 @@
@else
<div class="flex flex-col w-full gap-2 lg:flex-row">
@if ($is_multiline)
<x-forms.input isMultiline="{{ $is_multiline }}" id="key" />
<x-forms.textarea type="password" id="value" />
<x-forms.input :required="$is_redis_credential" isMultiline="{{ $is_multiline }}" id="key" />
<x-forms.textarea :required="$is_redis_credential" type="password" id="value" />
@else
<x-forms.input id="key" />
<x-forms.input type="password" id="value" />
<x-forms.input :disabled="$is_redis_credential" :required="$is_redis_credential" id="key" />
<x-forms.input :required="$is_redis_credential" type="password" id="value" />
@endif
@if ($is_shared)
<x-forms.input disabled type="password" id="real_value" />
<x-forms.input :disabled="$is_redis_credential" :required="$is_redis_credential" disabled type="password" id="real_value" />
@endif
</div>
@endif
<div class="flex flex-col w-full gap-2 lg:flex-row">
@if ($type === 'service')
<x-forms.checkbox instantSave id="is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" />
<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?" />
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@else
@if ($is_shared)
@if (!$is_redis_credential)
@if ($type === 'service')
<x-forms.checkbox instantSave id="is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" />
<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?" />
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@else
@if ($isSharedVariable)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@else
@if ($is_shared)
<x-forms.checkbox instantSave id="is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for dockerfile, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" />
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@if ($is_multiline === false)
<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?" />
<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?" />
@else
@if ($isSharedVariable)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@else
<x-forms.checkbox instantSave id="is_build_time"
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for dockerfile, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
label="Build Variable?" />
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@if ($is_multiline === false)
<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?" />
@endif
@endif
@endif
@endif

View File

@@ -2,45 +2,53 @@
<form wire:submit='submit' class="flex flex-col gap-2 xl:items-end xl:flex-row">
@if ($isReadOnly)
@if ($isFirst)
@if (
$storage->resource_type === 'App\Models\ServiceApplication' ||
$storage->resource_type === 'App\Models\ServiceDatabase')
<x-forms.input id="storage.name" label="Volume Name" required readonly
helper="Warning: Changing the volume name after the initial start could cause problems. Only use it when you know what are you doing." />
@else
<x-forms.input id="storage.name" label="Volume Name" required
helper="Warning: Changing the volume name after the initial start could cause problems. Only use it when you know what are you doing." />
@endif
@if ($isService || $startedAt)
<x-forms.input id="storage.host_path" readonly helper="Directory on the host system."
label="Source Path"
helper="Warning: Changing the source path after the initial start could cause problems. Only use it when you know what are you doing." />
<x-forms.input id="storage.mount_path" label="Destination Path"
helper="Directory inside the container." required readonly />
@else
<x-forms.input id="storage.host_path" helper="Directory on the host system." label="Source Path"
helper="Warning: Changing the source path after the initial start could cause problems. Only use it when you know what are you doing." />
<x-forms.input id="storage.mount_path" label="Destination Path"
helper="Directory inside the container." required readonly />
<x-forms.button type="submit">
Update
</x-forms.button>
@endif
<div class="flex gap-2 items-end w-full md:flex-row flex-col">
@if (
$storage->resource_type === 'App\Models\ServiceApplication' ||
$storage->resource_type === 'App\Models\ServiceDatabase')
<x-forms.input id="storage.name" label="Volume Name" required readonly
helper="Warning: Changing the volume name after the initial start could cause problems. Only use it when you know what are you doing." />
@else
<x-forms.input id="storage.name" label="Volume Name" required
helper="Warning: Changing the volume name after the initial start could cause problems. Only use it when you know what are you doing." />
@endif
@if ($isService || $startedAt)
<x-forms.input id="storage.host_path" readonly helper="Directory on the host system."
label="Source Path"
helper="Warning: Changing the source path after the initial start could cause problems. Only use it when you know what are you doing." />
<x-forms.input id="storage.mount_path" label="Destination Path"
helper="Directory inside the container." required readonly />
@else
<x-forms.input id="storage.host_path" helper="Directory on the host system." label="Source Path"
helper="Warning: Changing the source path after the initial start could cause problems. Only use it when you know what are you doing." />
<x-forms.input id="storage.mount_path" label="Destination Path"
helper="Directory inside the container." required readonly />
<x-forms.button type="submit">
Update
</x-forms.button>
@endif
</div>
@else
<x-forms.input id="storage.name" required readonly />
<x-forms.input id="storage.host_path" readonly />
<x-forms.input id="storage.mount_path" required readonly />
<div class="flex gap-2 items-end w-full">
<x-forms.input id="storage.name" required readonly />
<x-forms.input id="storage.host_path" readonly />
<x-forms.input id="storage.mount_path" required readonly />
</div>
@endif
@else
@if ($isFirst)
<x-forms.input id="storage.name" label="Volume Name" required />
<x-forms.input id="storage.host_path" helper="Directory on the host system." label="Source Path" />
<x-forms.input id="storage.mount_path" label="Destination Path" helper="Directory inside the container."
required />
<div class="flex gap-2 items-end w-full">
<x-forms.input id="storage.name" label="Volume Name" required />
<x-forms.input id="storage.host_path" helper="Directory on the host system." label="Source Path" />
<x-forms.input id="storage.mount_path" label="Destination Path"
helper="Directory inside the container." required />
</div>
@else
<x-forms.input id="storage.name" required />
<x-forms.input id="storage.host_path" />
<x-forms.input id="storage.mount_path" required />
<div class="flex gap-2 items-end w-full">
<x-forms.input id="storage.name" required />
<x-forms.input id="storage.host_path" />
<x-forms.input id="storage.mount_path" required />
</div>
@endif
<div class="flex gap-2">
<x-forms.button type="submit">

View File

@@ -25,7 +25,7 @@
</div>
@endif
@if (count($filteredTags) > 0)
<h3 class="pt-4">Exisiting Tags</h3>
<h3 class="pt-4">Existing Tags</h3>
<div>Click to add quickly</div>
<div class="flex flex-wrap gap-2 pt-4">
@foreach ($filteredTags as $tag)

View File

@@ -38,6 +38,95 @@
</div>
</div>
</div>
<div class="flex flex-col gap-4 pt-8">
<h3>CA SSL Certificate</h3>
<div class="flex gap-2">
<x-modal-confirmation
title="Confirm changing of CA Certificate?"
buttonTitle="Save Certificate"
submitAction="saveCaCertificate"
:actions="[
'This will overwrite the existing CA certificate at /data/coolify/ssl/coolify-ca.crt with your custom CA certificate.',
'This will regenerate all SSL certificates for databases on this server and it will sign them with your custom CA.',
'You must manually redeploy all your databases on this server so that they use the new SSL certificates singned with your new CA certificate.',
'Because of caching, you probably also need to redeploy all your resources on this server that are using this CA certificate.'
]"
confirmationText="/data/coolify/ssl/coolify-ca.crt"
shortConfirmationLabel="CA Certificate Path"
step3ButtonText="Save Certificate">
</x-modal-confirmation>
<x-modal-confirmation
title="Confirm Regenerate Certificate?"
buttonTitle="Regenerate Certificate"
submitAction="regenerateCaCertificate"
:actions="[
'This will generate a new CA certificate at /data/coolify/ssl/coolify-ca.crt and replace the existing one.',
'This will regenerate all SSL certificates for databases on this server and it will sign them with the new CA certificate.',
'You must manually redeploy all your databases on this server so that they use the new SSL certificates singned with the new CA certificate.',
'Because of caching, you probably also need to redeploy all your resources on this server that are using this CA certificate.'
]"
confirmationText="/data/coolify/ssl/coolify-ca.crt"
shortConfirmationLabel="CA Certificate Path"
step3ButtonText="Regenerate Certificate">
</x-modal-confirmation>
</div>
<div class="space-y-4">
<div class="text-sm">
<p class="font-medium mb-2">Recommended Configuration:</p>
<ul class="list-disc pl-5 space-y-1">
<li>Mount this CA certificate of Coolify into all containers that need to connect to one of your databases over SSL. You can see and copy the bind mount below.</li>
<li>Read more when and why this is needed <a class="underline" href="https://coolify.io/docs/databases/ssl" target="_blank">here</a>.</li>
</ul>
</div>
<div class="relative">
<x-forms.copy-button
text="- /data/coolify/ssl/coolify-ca.crt:/etc/ssl/certs/coolify-ca.crt:ro"
/>
</div>
</div>
<div>
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<span class="text-sm">CA Certificate</span>
@if($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>
<x-forms.button
wire:click="toggleCertificate"
type="button"
class="!py-1 !px-2 text-sm">
{{ $showCertificate ? 'Hide' : 'Show' }}
</x-forms.button>
</div>
@if($showCertificate)
<textarea
class="w-full h-[370px] input"
wire:model="certificateContent"
placeholder="Paste or edit CA certificate content here..."></textarea>
@else
<div class="w-full h-[370px] input">
<div class="h-full flex flex-col items-center justify-center text-gray-300">
<div class="mb-2">
━━━━━━━━ CERTIFICATE CONTENT ━━━━━━━━
</div>
<div class="text-sm">
Click "Show" to view or edit
</div>
</div>
</div>
@endif
</div>
</div>
</form>
</div>
</div>

View File

@@ -72,6 +72,7 @@
@script
<script>
$wire.$on('checkProxyEvent', () => {
$wire.$dispatch('info', 'Checking proxy.');
$wire.$call('checkProxy');
});
$wire.$on('restartEvent', () => {

View File

@@ -1,5 +1,7 @@
<div x-init="$wire.checkProxy()" class="flex gap-2">
<x-forms.button wire:click='checkProxy(true)' :showLoadingIndicator="false">Refresh</x-forms.button>
<div @if (data_get($server, 'proxy.force_stop', false) === false) x-init="$wire.checkProxy()" @endif class="flex gap-2">
@if (data_get($server, 'proxy.force_stop', false) === false)
<x-forms.button wire:click='checkProxy(true)' :showLoadingIndicator="false">Refresh</x-forms.button>
@endif
@if (data_get($server, 'proxy.status') === 'running')
<x-status.running status="Proxy Running" />
@elseif (data_get($server, 'proxy.status') === 'restarting')

View File

@@ -12,7 +12,7 @@
@if ($server->id === 0)
<x-modal-confirmation title="Confirm Server Settings Change?" buttonTitle="Save"
submitAction="submit" :actions="[
'If you missconfigure the server, you could lose a lot of functionalities of Coolify.',
'If you misconfigure the server, you could lose a lot of functionalities of Coolify.',
]" :confirmWithText="false" :confirmWithPassword="false"
step2ButtonText="Save" />
@else

View File

@@ -6,7 +6,7 @@
<div class="flex flex-col">
<div class="flex items-center gap-2">
<h2>Backup</h2>
@if (isset($database))
@if (isset($database) && $server->isFunctional())
<x-forms.button type="submit" wire:click="submit">
Save
</x-forms.button>
@@ -14,26 +14,39 @@
</div>
<div class="pb-4">Backup configuration for Coolify instance.</div>
<div>
@if (isset($database) && isset($backup))
<div class="flex flex-col gap-3 pb-4">
<div class="flex gap-2">
<x-forms.input label="UUID" readonly id="uuid" />
<x-forms.input label="Name" readonly id="name" />
<x-forms.input label="Description" id="description" />
@if ($server->isFunctional())
@if (isset($database) && isset($backup))
<div class="flex flex-col gap-3 pb-4">
<div class="flex gap-2">
<x-forms.input label="UUID" readonly id="uuid" />
<x-forms.input label="Name" readonly id="name" />
<x-forms.input label="Description" id="description" />
</div>
<div class="flex gap-2">
<x-forms.input label="User" readonly id="postgres_user" />
<x-forms.input type="password" label="Password" readonly id="postgres_password" />
</div>
</div>
<div class="flex gap-2">
<x-forms.input label="User" readonly id="postgres_user" />
<x-forms.input type="password" label="Password" readonly id="postgres_password" />
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
<div class="py-4">
<livewire:project.database.backup-executions :backup="$backup" />
</div>
</div>
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
<div class="py-4">
<livewire:project.database.backup-executions :backup="$backup" />
</div>
@else
To configure automatic backup for your Coolify instance, you first need to add a database resource
into Coolify.
<x-forms.button class="mt-2" wire:click="addCoolifyDatabase">Configure Backup</x-forms.button>
@endif
@else
To configure automatic backup for your Coolify instance, you first need to add a database resource
into Coolify.
<x-forms.button class="mt-2" wire:click="addCoolifyDatabase">Configure Backup</x-forms.button>
<div class="p-6 bg-red-500/10 rounded-lg border border-red-500/20">
<div class="text-red-500 font-medium mb-4">
Instance Backup is currently disabled because the localhost server is not properly validated.
Please validate your server to enable Instance Backup.
</div>
<a href="{{ route('server.show', [$server->uuid]) }}"
class="text-black hover:text-gray-700 dark:text-white dark:hover:text-gray-200 underline">
Go to Server Settings to Validate
</a>
</div>
@endif
</div>
</div>

View File

@@ -13,7 +13,7 @@
<x-modal-input buttonTitle="Send Test Email" title="Send Test Email">
<form wire:submit.prevent="sendTestEmail" class="flex flex-col w-full gap-2">
<x-forms.input wire:model="testEmailAddress" placeholder="test@example.com" id="testEmailAddress"
label="Recipients" required />
label="Recipient" required />
<x-forms.button type="submit" @click="modalOpen=false">
Send Email
</x-forms.button>

View File

@@ -90,8 +90,6 @@
</div>
<h4 class="pt-6">API</h4>
<div class="pb-4">For API documentation, please visit <a class="dark:text-warning underline"
href="/docs/api">/docs/api</a></div>
<div class="md:w-96 pb-2">
<x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" />
</div>
@@ -141,7 +139,7 @@
<x-modal-confirmation title="Disable Two Step Confirmation?"
buttonTitle="Disable Two Step Confirmation" isErrorButton submitAction="toggleTwoStepConfirmation"
:actions="[
'Two Step confimation will be disabled globally.',
'Two Step confirmation will be disabled globally.',
'Disabling two step confirmation reduces security (as anyone can easily delete anything).',
'The risk of accidental actions will increase.',
]" confirmationText="DISABLE TWO STEP CONFIRMATION"

View File

@@ -1,5 +1,5 @@
<form wire:submit='viaLink' class="flex flex-col items-start gap-2 lg:items-end lg:flex-row">
<div class="flex flex-1 gap-2">
<form wire:submit='viaLink' class="flex gap-2 flex-col lg:flex-row items-end">
<div class="flex flex-1 lg:w-fit w-full gap-2">
<x-forms.input id="email" type="email" label="Email" name="email" placeholder="Email" required />
<x-forms.select id="role" name="role" label="Role">
@if (auth()->user()->role() === 'owner')
@@ -9,7 +9,7 @@
<option value="member">Member</option>
</x-forms.select>
</div>
<div class="flex gap-2">
<div class="flex gap-2 lg:w-fit w-full">
<x-forms.button type="submit">Generate Invitation Link</x-forms.button>
@if (is_transactional_emails_enabled())
<x-forms.button wire:click.prevent='viaEmail'>Send Invitation via Email</x-forms.button>