This commit is contained in:
Joao Patricio
2023-05-12 19:22:16 +01:00
128 changed files with 3944 additions and 650 deletions

View File

@@ -1,20 +1,46 @@
/* @tailwind base; */
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
@apply bg-neutral-900 text-white font-sans;
}
a, a:visited {
@apply text-neutral-300 hover:text-purple-500;
@apply bg-coolgray-100 text-white font-sans;
}
input, textarea {
@apply border-none p-2 bg-neutral-800 text-white disabled:text-neutral-600 read-only:text-neutral-600 read-only:select-none
@apply border-none p-2 bg-coolgray-200 text-white disabled:text-neutral-600 read-only:text-neutral-600 read-only:select-none outline-none ;
}
select {
@apply border-none p-2 bg-neutral-800 text-white disabled:text-neutral-600 read-only:select-none
@apply border-none p-2 bg-coolgray-200 text-white disabled:text-neutral-600 read-only:select-none outline-none;
}
button {
@apply border-none px-2 p-1 cursor-pointer;
}
.main-menu {
@apply relative float-left;
}
.main-menu:after {
content: '/';
@apply absolute right-0 top-0 text-neutral-400 px-2 pt-[0.3rem];
}
.magic-input {
@apply w-[25rem] rounded shadow outline-none focus:bg-neutral-700 text-white;
}
.magic-items {
@apply absolute top-14 w-[25rem] bg-coolgray-200 border-b-2 border-r-2 border-l-2 border-solid border-coolgray-100 rounded-b;
}
.magic-item {
@apply m-2 py-2 pl-4 cursor-pointer hover:bg-neutral-700 text-neutral-300 hover:text-white;
}
.magic-item-focused {
@apply bg-neutral-700 text-white;
}
h1 {
@apply text-3xl font-bold py-4;
}
h2 {
@apply text-xl font-bold py-4;
}
.box {
@apply flex items-center justify-center text-sm rounded cursor-pointer h-14 bg-coolgray-200 hover:bg-coollabs p-2;
}

View File

@@ -1,4 +1,9 @@
// import './bootstrap';
import Alpine from 'alpinejs'
window.Alpine = Alpine
Alpine.start()
import Alpine from "alpinejs";
// import { createApp } from "vue";
// import MagicSearchBar from "./components/MagicSearchBar.vue";
window.Alpine = Alpine;
Alpine.start();
// const app = createApp({});
// app.component('magic-search-bar', MagicSearchBar);
// app.mount('#vue');

View File

@@ -1,5 +1,5 @@
<x-layout>
<div>v{{ config('coolify.version') }}</div>
<div>v{{ config('version') }}</div>
<a href="/login">Login</a>
@if ($is_registration_enabled)
<a href="/register">Register</a>

View File

@@ -1,5 +1,5 @@
<x-layout>
<div>v{{ config('coolify.version') }}</div>
<div>v{{ config('version') }}</div>
<a href="/login">Login</a>
<a href="/register">Register</a>
<form action="/register" method="POST">

View File

@@ -1,3 +1,3 @@
<x-layout>
<livewire:run-command />
<livewire:run-command :servers="$servers" />
</x-layout>

View File

@@ -1,5 +1,22 @@
<nav class="flex gap-4 py-2">
<a href="{{ route('project.application.configuration', Route::current()->parameters()) }}">Configuration</a>
<a href="{{ route('project.application.deployments', Route::current()->parameters()) }}">Deployments</a>
<a target="_blank" href="{{ $gitBranchLocation }}">
<x-inputs.button>Open on Git ↗️</x-inputs.button>
</a>
<a
href="{{ route('project.application.configuration', [
'project_uuid' => Route::current()->parameters()['project_uuid'],
'application_uuid' => Route::current()->parameters()['application_uuid'],
'environment_name' => Route::current()->parameters()['environment_name'],
]) }}">
<x-inputs.button>Configuration</x-inputs.button>
</a>
<a
href="{{ route('project.application.deployments', [
'project_uuid' => Route::current()->parameters()['project_uuid'],
'application_uuid' => Route::current()->parameters()['application_uuid'],
'environment_name' => Route::current()->parameters()['environment_name'],
]) }}">
<x-inputs.button>Deployments</x-inputs.button>
</a>
<livewire:project.application.deploy :applicationId="$applicationId" />
</nav>

View File

@@ -1,19 +1,25 @@
@props([
'isWarning' => null,
'defaultClass' => 'text-white bg-neutral-800 hover:bg-violet-600 w-28 h-6',
'defaultWarningClass' => 'text-white bg-red-500 hover:bg-red-600 w-28 h-6',
'loadingClass' => 'text-black bg-green-500 w-28 h-6',
'disabled' => null,
'defaultClass' => 'text-white hover:bg-coollabs h-8 rounded transition-colors',
'defaultWarningClass' => 'text-white bg-red-500 hover:bg-red-600 h-8 rounded',
'disabledClass' => 'text-coolgray-200 h-8 rounded',
'loadingClass' => 'text-black bg-green-500 h-8 rounded',
'confirm' => null,
'confirmAction' => null,
])
<button {{ $attributes }} @class([
$defaultClass => !$confirm && !$isWarning,
$defaultWarningClass => $confirm || $isWarning,
]) @if ($attributes->whereStartsWith('wire:click'))
$defaultClass => !$confirm && !$isWarning && !$disabled,
$defaultWarningClass => ($confirm || $isWarning) && !$disabled,
$disabledClass => $disabled,
]) @if ($attributes->whereStartsWith('wire:click') && !$disabled)
wire:target="{{ explode('(', $attributes->whereStartsWith('wire:click')->first())[0] }}"
wire:loading.delay.class="{{ $loadingClass }}" wire:loading.delay.attr="disabled"
wire:loading.delay.class.remove="{{ $defaultClass }} {{ $attributes->whereStartsWith('class')->first() }}"
@endif
@if ($disabled !== null)
disabled title="{{ $disabled }}"
@endif
@isset($confirm)
x-on:click="toggleConfirmModal('{{ $confirm }}', '{{ explode('(', $confirmAction)[0] }}')"
@endisset

View File

@@ -6,15 +6,16 @@
'instantSave' => $attributes->has('instantSave'),
'noLabel' => $attributes->has('noLabel'),
'noDirty' => $attributes->has('noDirty'),
'hidden' => $attributes->has('hidden'),
])
<span @class([
'flex justify-end' => $type === 'checkbox',
'flex' => $type === 'checkbox',
'flex flex-col' => $type !== 'checkbox',
])>
@if (!$noLabel)
<label for={{ $id }} @if (!$noDirty) wire:dirty.class="text-amber-300" @endif
wire:target={{ $id }}>
@if ($hidden) class="hidden" @endif wire:target={{ $id }}>
@if ($label)
{{ $label }}
@else
@@ -23,6 +24,7 @@
@if ($required)
*
@endif
</label>
@endif
@if ($type === 'textarea')
@@ -31,8 +33,9 @@
@else
<input {{ $attributes }} @if ($required) required @endif
@if (!$noDirty) wire:dirty.class="text-black bg-amber-300" @endif
type={{ $type }} id={{ $id }}
@if ($instantSave) wire:click='instantSave' wire:model.defer={{ $id }} @else wire:model.defer={{ $value ?? $id }} @endif />
type={{ $type }} id={{ $id }} name={{ $id }}
@if ($instantSave) wire:click='instantSave' wire:model.defer={{ $id }} @else wire:model.defer={{ $value ?? $id }} @endif
@if ($hidden) class="hidden" @endif />
@endif
@error($id)
<div class="text-red-500">{{ $message }}</div>

View File

@@ -4,6 +4,8 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin>
<link href="https://api.fonts.coollabs.io/css2?family=Inter&display=swap" rel="stylesheet">
@env('local')
<title>Coolify - localhost</title>
@endenv
@@ -21,16 +23,28 @@
</head>
<body>
<a
class="fixed text-xs cursor-pointer left-2 bottom-1 opacity-20 hover:opacity-100 hover:text-white">v{{ config('version') }}</a>
@livewireScripts
@auth
<x-navbar />
@endauth
<main>
<main class="max-w-6xl pt-10 mx-auto">
{{ $slot }}
</main>
@livewireScripts
@auth
<script>
window.addEventListener("keydown", function(event) {
if (event.target.nodeName === 'BODY') {
if (event.key === '/') {
event.preventDefault();
window.dispatchEvent(new CustomEvent('slash'));
}
}
})
function checkIfIamDead() {
console.log('Checking server\'s pulse...')
checkIfIamDeadInterval = setInterval(async () => {
@@ -76,10 +90,16 @@
window.location.reload();
})
Livewire.on('error', (message) => {
console.log(message);
alert(message);
})
Livewire.on('saved', (message) => {
if (message) console.log(message);
else console.log('saved');
})
</script>
@endauth
</body>
</html>

View File

@@ -0,0 +1,541 @@
<div x-data="magicsearchbar" @slash.window="mainMenu = true" class="pt-2">
{{-- Main --}}
<template x-cloak x-if="isMainMenu">
<div>
<div class="main-menu">
<input class="magic-input" x-ref="search" x-model="search" x-on:click="checkMainMenu"
x-on:click.outside="closeMenus" placeholder="Search, jump or create... magically... 🪄"
x-on:keyup.escape="clearSearch" x-on:keydown.down="focusNext(items.length)"
x-on:keydown.up="focusPrev(items.length)"
x-on:keyup.enter="focusedIndex !== '' && await set(filteredItems()[focusedIndex]?.next ?? 'server',filteredItems()[focusedIndex]?.name)" />
</div>
<div x-show="mainMenu" class="magic-items">
<template x-for="(item,index) in filteredItems" :key="item.name">
<div x-on:click="await set(item.next ?? 'server',item.name)"
:class="focusedIndex === index && 'magic-item-focused'" class="magic-item">
<span class="px-2 mr-1 text-xs text-white bg-green-600 rounded" x-show="item.type === 'Add'"
x-text="item.type"></span>
<span class="px-2 mr-1 text-xs text-white bg-purple-600 rounded" x-show="item.type === 'Jump'"
x-text="item.type"></span>
<span class="px-2 mr-1 text-xs text-white bg-blue-600 rounded" x-show="item.type === 'New'"
x-text="item.type"></span>
<span x-text="item.name"></span>
</div>
</template>
</div>
</div>
</template>
{{-- Servers --}}
<template x-cloak x-if="serverMenu">
<div x-on:click.outside="closeMenus">
<input class="magic-input" x-ref="search" x-model="search" placeholder="Select a server..."
x-on:keydown.down="focusNext(servers.length)" x-on:keydown.up="focusPrev(servers.length)"
x-on:keyup.enter="focusedIndex !== '' && await set('destination',filteredServers()[focusedIndex].uuid)" />
<div class="magic-items">
<template x-if="servers.length === 0">
<div class="magic-item" x-on:click="set('newServer')">
<span>No server found. Click here to add a new one!</span>
</div>
</template>
<template x-for="(server,index) in filteredServers" :key="server.name ?? server">
<div x-on:click="await set('destination',server.uuid)"
:class="focusedIndex === index && 'magic-item-focused'" class="magic-item">
<span class="px-2 mr-1 text-xs text-white bg-purple-600 rounded">Server</span>
<span x-text="server.name"></span>
</div>
</template>
</div>
</div>
</template>
{{-- Destinations --}}
<template x-cloak x-if="destinationMenu">
<div x-on:click.outside="closeMenus">
<input class="magic-input" x-ref="search" x-model="search" placeholder="Select a destination..."
x-on:keydown.down="focusNext(destinations.length)" x-on:keydown.up="focusPrev(destinations.length)"
x-on:keyup.escape="closeMenus"
x-on:keyup.enter="focusedIndex !== '' && await set('project',filteredDestinations()[focusedIndex].uuid)" />
<div class="magic-items">
<template x-if="destinations.length === 0">
<div class="magic-item" x-on:click="set('newDestination')">
<span>No destination found. Click here to add a new one!</span>
</div>
</template>
<template x-for="(destination,index) in filteredDestinations" :key="destination.name ?? destination">
<div x-on:click="await set('project',destination.uuid)"
:class="focusedIndex === index && 'magic-item-focused'" class="magic-item">
<span class="px-2 mr-1 text-xs text-white bg-purple-700 rounded">Destination</span>
<span x-text="destination.name"></span>
</div>
</template>
</div>
</div>
</template>
{{-- Project --}}
<template x-cloak x-if="projectMenu">
<div x-on:click.outside="closeMenus">
<input class="magic-input" x-ref="search" x-model="search" placeholder="Type your project name..."
x-on:keydown.down="focusNext(projects.length + 1)" x-on:keydown.up="focusPrev(projects.length + 1)"
x-on:keyup.escape="closeMenus"
x-on:keyup.enter="focusedIndex !== '' && await set('environment',filteredProjects()[focusedIndex - 1]?.uuid)" />
<div class="magic-items">
<div x-on:click="await newProject" class="magic-item"
:class="focusedIndex === 0 && 'magic-item-focused'">
<span>New Project</span>
<span x-text="search"></span>
</div>
<template x-for="(project,index) in filteredProjects" :key="project.name ?? project">
<div x-on:click="await set('environment',project.uuid)"
:class="focusedIndex === index + 1 && 'magic-item-focused'" class="magic-item">
<span class="px-2 mr-1 text-xs text-white bg-purple-700 rounded">Project</span>
<span x-text="project.name"></span>
</div>
</template>
</div>
</div>
</template>
{{-- Environments --}}
<template x-cloak x-if="environmentMenu">
<div x-on:click.outside="closeMenus">
<input class="magic-input" x-ref="search" x-model="search" placeholder="Select a environment..."
x-on:keydown.down="focusNext(environments.length + 1)"
x-on:keydown.up="focusPrev(environments.length + 1)" x-on:keyup.escape="closeMenus"
x-on:keyup.enter="focusedIndex !== '' && await set('jump',filteredEnvironments()[focusedIndex - 1]?.name)" />
<div class="magic-items">
<div x-on:click="await newEnvironment" class="magic-item"
:class="focusedIndex === 0 && 'magic-item-focused'">
<span>New Environment</span>
<span x-text="search"></span>
</div>
<template x-for="(environment,index) in filteredEnvironments" :key="environment.name ?? environment">
<div x-on:click="await set('jump',environment.name)"
:class="focusedIndex === index + 1 && 'magic-item-focused'" class="magic-item">
<span class="px-2 mr-1 text-xs text-white bg-purple-700 rounded">Env</span>
<span x-text="environment.name"></span>
</div>
</template>
</div>
</div>
</template>
{{-- Projects --}}
<template x-cloak x-if="projectsMenu">
<div x-on:click.outside="closeMenus">
<input x-ref="search" x-model="search" class="magic-input" placeholder="Select a project..."
x-on:keyup.escape="closeMenus" x-on:keydown.down="focusNext(projects.length)"
x-on:keydown.up="focusPrev(projects.length)"
x-on:keyup.enter="focusedIndex !== '' && await set('jumpToProject',filteredProjects()[focusedIndex]?.uuid)" />
<div class="magic-items">
<template x-if="projects.length === 0">
<div class="magic-item hover:bg-neutral-800">
<span>No projects found.</span>
</div>
</template>
<template x-for="(project,index) in filteredProjects" :key="project.name ?? project">
<div x-on:click="await set('jumpToProject',project.uuid)"
:class="focusedIndex === index && 'magic-item-focused'"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs text-white bg-purple-700 rounded">Jump</span>
<span x-text="project.name"></span>
</div>
</template>
</div>
</div>
</template>
{{-- Destinations --}}
<template x-cloak x-if="destinationsMenu">
<div x-on:click.outside="closeMenus">
<input x-ref="search" x-model="search" class="magic-input" placeholder="Select a destination..."
x-on:keyup.escape="closeMenus" x-on:keydown.down="focusNext(destinations.length)"
x-on:keydown.up="focusPrev(destinations.length)"
x-on:keyup.enter="focusedIndex !== '' && await set('jumpToDestination',filteredDestinations()[focusedIndex].uuid)" />
<div class="magic-items">
<template x-if="destinations.length === 0">
<div class="magic-item" x-on:click="set('newDestination')">
<span>No destination found. Click here to add a new one!</span>
</div>
</template>
<template x-for="(destination,index) in filteredDestinations" :key="destination.name ?? destination">
<div x-on:click="await set('jumpToDestination',destination.uuid)"
:class="focusedIndex === index && 'magic-item-focused'"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-purple-700 rounded">Jump</span>
<span x-text="destination.name"></span>
</div>
</template>
</div>
</div>
</template>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('magicsearchbar', () => ({
isMainMenu() {
return !this.serverMenu &&
!this.destinationMenu &&
!this.projectMenu &&
!this.environmentMenu &&
!this.projectsMenu &&
!this.destinationsMenu
},
focus() {
if (this.$refs.search) this.$refs.search.focus()
},
init() {
this.$watch('search', () => {
this.focusedIndex = ""
})
this.$watch('mainMenu', () => {
this.focus()
})
this.$watch('serverMenu', () => {
this.focus()
})
this.$watch('destinationMenu', () => {
this.focus()
})
this.$watch('projectMenu', () => {
this.focus()
})
this.$watch('environmentMenu', () => {
this.focus()
})
},
mainMenu: false,
serverMenu: false,
destinationMenu: false,
destinationsMenu: false,
projectMenu: false,
projectsMenu: false,
environmentMenu: false,
search: '',
selectedAction: '',
selectedServer: '',
selectedDestination: '',
selectedProject: '',
selectedEnvironment: '',
servers: ['Loading...'],
destinations: ['Loading...'],
projects: ['Loading...'],
environments: ['Loading...'],
focusedIndex: "",
items: [{
name: 'Server',
type: 'Add',
tags: 'new,server',
next: 'newServer'
},
{
name: 'Destination',
type: 'Add',
tags: 'new,destination',
next: 'newDestination'
},
{
name: 'Private Key',
type: 'Add',
tags: 'new,private-key,ssh,key',
next: 'newPrivateKey'
},
{
name: 'Source',
type: 'Add',
tags: 'new,source,github,gitlab,bitbucket',
next: 'newSource'
},
{
name: 'Public Repository',
type: 'Add',
tags: 'application,public,repository,github,gitlab,bitbucket,git',
},
{
name: 'Private Repository (with GitHub App)',
type: 'Add',
tags: 'application,private,repository,github,gitlab,bitbucket,git',
},
{
name: 'Private Repository (with Deploy Key)',
type: 'Add',
tags: 'application,private,repository,github,gitlab,bitbucket,git',
},
{
name: 'Database',
type: 'Add',
tags: 'data,database,mysql,postgres,sql,sqlite,redis,mongodb,maria,percona',
},
{
name: 'Servers',
type: 'Jump',
tags: 'servers',
next: 'server'
},
{
name: 'Projects',
type: 'Jump',
tags: 'projects',
next: 'projects'
},
{
name: 'Destinations',
type: 'Jump',
tags: 'destinations',
next: 'destinations'
}
],
focusPrev(maxLength) {
if (this.focusedIndex === "") {
this.focusedIndex = maxLength - 1
} else {
if (this.focusedIndex > 0) {
this.focusedIndex = this.focusedIndex - 1
}
}
},
focusNext(maxLength) {
if (this.focusedIndex === "") {
this.focusedIndex = 0
} else {
if (maxLength > this.focusedIndex + 1) {
this.focusedIndex = this.focusedIndex + 1
}
}
},
clearSearch() {
if (this.search === '') {
this.closeMenus()
this.$nextTick(() => {
if (this.$refs.search) this.$refs.search.blur();
})
return
}
this.search = ''
this.focusedIndex = ''
},
closeMenus() {
if (this.$refs.search) this.$refs.search.blur();
this.search = ''
this.focusedIndex = ''
this.mainMenu = false
this.serverMenu = false
this.destinationMenu = false
this.projectMenu = false
this.environmentMenu = false
},
checkMainMenu() {
if (this.serverMenu) return
this.mainMenu = true
},
filteredItems() {
if (this.search === '') return this.items
return this.items.filter(item => {
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
item.tags.toLowerCase().includes(this.search.toLowerCase())
})
},
filteredServers() {
if (this.search === '') return this.servers
return this.servers.filter(server => {
return server.name.toLowerCase().includes(this.search
.toLowerCase())
})
},
filteredDestinations() {
if (this.search === '') return this.destinations
if (this.destinations.length === 0) return []
return this.destinations.filter(destination => {
return destination.name.toLowerCase().includes(this.search
.toLowerCase())
})
},
filteredProjects() {
if (this.search === '') return this.projects
return this.projects.filter(project => {
return project.name.toLowerCase().includes(this.search
.toLowerCase())
})
},
filteredEnvironments() {
if (this.search === '') return this.environments
return this.environments.filter(environment => {
return environment.name.toLowerCase().includes(this.search
.toLowerCase())
})
},
async newProject() {
const response = await fetch('/magic?server=' + this.selectedServer +
'&destination=' + this.selectedDestination +
'&project=new&name=' + this.search);
if (response.ok) {
const {
project_uuid
} = await response.json();
this.set('environment', project_uuid)
this.set('jump', 'production')
}
},
async newEnvironment() {
const response = await fetch('/magic?server=' + this.selectedServer +
'&destination=' + this.selectedDestination +
'&project=' + this.selectedProject + '&environment=new&name=' + this
.search);
if (response.ok) {
const {
environment_name
} = await response.json();
this.set('jump', environment_name)
}
},
async set(action, id) {
let response = null
switch (action) {
case 'server':
this.items.find((item, index) => {
if (item.name.toLowerCase() === id
.toLowerCase()) {
return this.selectedAction = index
}
})
response = await fetch('/magic?servers=true');
if (response.ok) {
const {
servers
} = await response.json();
this.servers = servers;
}
this.closeMenus()
this.serverMenu = true
break
case 'destination':
this.selectedServer = id
if (this.items[this.selectedAction].type === "Jump") {
return window.location = '/server/' + id
}
response = await fetch('/magic?server=' + this
.selectedServer +
'&destinations=true');
if (response.ok) {
const {
destinations
} = await response.json();
this.destinations = destinations;
}
this.closeMenus()
this.destinationMenu = true
break
case 'project':
this.selectedDestination = id
response = await fetch('/magic?server=' + this
.selectedServer +
'&destination=' + this.selectedDestination +
'&projects=true');
if (response.ok) {
const {
projects
} = await response.json();
this.projects = projects;
}
this.closeMenus()
this.projectMenu = true
break
case 'projects':
response = await fetch('/magic?projects=true');
if (response.ok) {
const {
projects
} = await response.json();
this.projects = projects;
}
this.closeMenus()
this.projectsMenu = true
break
case 'destinations':
response = await fetch('/magic?destinations=true');
if (response.ok) {
const {
destinations
} = await response.json();
this.destinations = destinations;
}
this.closeMenus()
this.destinationsMenu = true
break
case 'environment':
if (this.focusedIndex === 0) {
this.focusedIndex = ''
return await this.newProject()
}
this.selectedProject = id
response = await fetch('/magic?server=' + this
.selectedServer +
'&destination=' + this.selectedDestination +
'&project=' + this
.selectedProject + '&environments=true');
if (response.ok) {
const {
environments
} = await response.json();
this.environments = environments;
}
this.closeMenus()
this.environmentMenu = true
break
case 'jump':
if (this.focusedIndex === 0) {
this.focusedIndex = ''
return await this.newEnvironment()
}
this.selectedEnvironment = id
if (this.selectedAction === 0) {
window.location =
`/project/${this.selectedProject}/${this.selectedEnvironment}/new?type=public&destination=${this.selectedDestination}`
} else if (this.selectedAction === 1) {
window.location =
`/project/${this.selectedProject}/${this.selectedEnvironment}/new?type=private-gh-app&destination=${this.selectedDestination}`
} else if (this.selectedAction === 2) {
window.location =
`/project/${this.selectedProject}/${this.selectedEnvironment}/new?type=private-deploy-key&destination=${this.selectedDestination}`
} else if (this.selectedAction === 3) {
console.log('new Database')
}
this.closeMenus()
break
case 'jumpToProject':
window.location = `/project/${id}`
this.closeMenus()
break
case 'jumpToDestination':
window.location = `/destination/${id}`
this.closeMenus()
break
case 'newServer':
window.location = `/server/new`
this.closeMenus()
break
case 'newDestination':
if (this.selectedServer !== '') {
window.location = `/destination/new?server=${this.selectedServer}`
return
}
window.location = `/destination/new`
this.closeMenus()
break
case 'newPrivateKey':
window.location = `/private-key/new`
this.closeMenus()
break
case 'newSource':
window.location = `/source/new`
this.closeMenus()
break
}
}
}))
})
</script>

View File

@@ -1,17 +1,31 @@
<nav class="flex gap-2 ">
<div>v{{ config('coolify.version') }}</div>
<nav class="flex gap-2">
@auth
<a href="/">Home</a>
<a href="/command-center">Command Center</a>
<a href="/profile">Profile</a>
@if (auth()->user()->isRoot())
<a href="/settings">Settings</a>
@endif
<form action="/logout" method="POST">
@csrf
<x-inputs.button type="submit">Logout</x-inputs.button>
</form>
<livewire:check-update />
<livewire:force-upgrade />
<div class="fixed flex gap-2 left-2 top-2">
<a href="/">
<x-inputs.button>Home</x-inputs.button>
</a>
<a href="/command-center">
<x-inputs.button>Command Center</x-inputs.button>
</a>
<a href="/profile">
<x-inputs.button>Profile</x-inputs.button>
</a>
@if (auth()->user()->isRoot())
<a href="/settings">
<x-inputs.button>Settings</x-inputs.button>
</a>
@endif
</div>
<div class="flex-1"></div>
<x-magic-bar />
<div class="flex-1"></div>
<div class="fixed flex gap-2 right-2 top-2">
{{-- <livewire:check-update /> --}}
<livewire:force-upgrade />
<form action="/logout" method="POST">
@csrf
<x-inputs.button type="submit">Logout</x-inputs.button>
</form>
</div>
@endauth
</nav>

View File

@@ -1,34 +1,56 @@
<x-layout>
<h1>Projects <a href="{{ route('project.new') }}">
<x-inputs.button>New</x-inputs.button>
</a></h1>
@forelse ($projects as $project)
<a href="{{ route('project.environments', [$project->uuid]) }}">{{ data_get($project, 'name') }}</a>
@empty
<p>No projects found.</p>
@endforelse
<h1>Servers <a href="{{ route('server.new') }}">
<x-inputs.button>New</x-inputs.button>
</a></h1>
@forelse ($servers as $server)
<a href="{{ route('server.show', [$server->uuid]) }}">{{ data_get($server, 'name') }}</a>
@empty
<p>No servers found.</p>
@endforelse
<h1>Destinations <a href="{{ route('destination.new') }}">
<x-inputs.button>New</x-inputs.button>
</a></h1>
@forelse ($destinations as $destination)
<a href="{{ route('destination.show', [$destination->uuid]) }}">{{ data_get($destination, 'name') }}</a>
@empty
<p>No destinations found.</p>
@endforelse
<h1>Private Keys <a href="{{ route('private-key.new') }}">
<x-inputs.button>New</x-inputs.button>
</a></h1>
@forelse ($private_keys as $private_key)
<a href="{{ route('private-key.show', [$private_key->uuid]) }}">{{ data_get($private_key, 'name') }}</a>
@empty
<p>No servers found.</p>
@endforelse
@if ($servers->count() === 0)
<div class="flex flex-col items-center justify-center h-full pt-32">
<div class="pb-3">Without a server, you won't be able to do much.</div>
<div class="text-2xl">Let's create <a href="{{ route('server.new') }}"
class="p-2 rounded bg-coollabs hover:bg-coollabs-100">your
first</a> one!</div>
</div>
@else
<h1>Projects </h1>
<div class="flex gap-2">
@forelse ($projects as $project)
<a href="{{ route('project.environments', [$project->uuid]) }}"
class="box">{{ data_get($project, 'name') }}</a>
@empty
<p>No projects found.</p>
@endforelse
</div>
<h1>Servers </h1>
<div class="flex gap-2">
@forelse ($servers as $server)
<a href="{{ route('server.show', [$server->uuid]) }}" class="box">{{ data_get($server, 'name') }}</a>
@empty
<p>No servers found.</p>
@endforelse
</div>
<h1>Destinations </h1>
<div class="flex gap-2">
@forelse ($destinations as $destination)
<a href="{{ route('destination.show', [$destination->uuid]) }}"
class="box">{{ data_get($destination, 'name') }}</a>
@empty
<p>No destinations found.</p>
@endforelse
</div>
<h1>Private Keys </h1>
<div class="flex gap-2">
@forelse ($private_keys as $private_key)
<a href="{{ route('private-key.show', [$private_key->uuid]) }}"
class="box">{{ data_get($private_key, 'name') }}</a>
@empty
<p>No servers found.</p>
@endforelse
</div>
<h1>GitHub Apps </h1>
<div class="flex">
@forelse ($github_apps as $github_app)
<a href="{{ route('source.github.show', [$github_app->uuid]) }}"
class="box">{{ data_get($github_app, 'name') }}</a>
@empty
<p>No servers found.</p>
@endforelse
</div>
@endif
</x-layout>

View File

@@ -1,4 +1,4 @@
<x-layout>
<h1>New Destination</h1>
<livewire:destination.new.standalone-docker :servers="$servers" />
<livewire:destination.new.standalone-docker :servers="$servers" :server_id="$server_id" />
</x-layout>

View File

@@ -1,7 +1,10 @@
<div>
@isset($this->activity)
<span>Status: {{ $this->activity?->properties->get('status') }}</span>
<pre class="flex flex-col-reverse w-full overflow-y-scroll"
@if ($isPollingActive) wire:poll.750ms="polling" @endif>{{ \App\Actions\CoolifyTask\RunRemoteProcess::decodeOutput($this->activity) }}</pre>
@endisset
<div
class="flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4">
@if ($this->activity)
<pre class="whitespace-pre-wrap" @if ($isPollingActive) wire:poll.750ms="polling" @endif>{{ \App\Actions\CoolifyTask\RunRemoteProcess::decodeOutput($this->activity) }}</pre>
@else
<pre class="whitespace-pre-wrap">Output will be here...</pre>
@endif
</div>
</div>

View File

@@ -1,6 +1,6 @@
<div>
<x-inputs.button class="w-32 text-white bg-neutral-800 hover:bg-violet-600" wire:click='checkUpdate' type="submit">
Check for updates</x-inputs.button>
<x-inputs.button wire:click='checkUpdate' type="submit">
Check Update</x-inputs.button>
@if ($updateAvailable)
Update available
@endif

View File

@@ -10,7 +10,7 @@
<x-inputs.button>
Submit
</x-inputs.button>
<x-inputs.button isWarning x-on:click="deleteDestination = true">
<x-inputs.button isWarning x-on:click.prevent="deleteDestination = true">
Delete
</x-inputs.button>
</div>

View File

@@ -7,7 +7,7 @@
<x-inputs.button type="submit">
Submit
</x-inputs.button>
<x-inputs.button isWarning x-on:click="deletePrivateKey = true">
<x-inputs.button isWarning x-on:click.prevent="deletePrivateKey = true">
Delete
</x-inputs.button>
</form>

View File

@@ -1,10 +1,10 @@
<div>
<form class="flex flex-col gap-2 w-96" wire:submit.prevent='createPrivateKey'>
<x-inputs.input id="private_key_name" label="Name" required />
<x-inputs.input id="private_key_description" label="Longer Description" />
<x-inputs.input type="textarea" id="private_key_value" label="Private Key" required />
<x-inputs.button type="submit">
Submit
<form class="flex flex-col gap-2 " wire:submit.prevent='createPrivateKey'>
<x-inputs.input id="name" label="Name" required />
<x-inputs.input id="description" label="Description" />
<x-inputs.input type="textarea" id="value" label="Private Key" required />
<x-inputs.button type="submit" wire.click.prevent>
Save Private Key
</x-inputs.button>
</form>
</div>

View File

@@ -8,7 +8,7 @@
<x-inputs.button wire:click='start'>Start</x-inputs.button>
<x-inputs.button wire:click='forceRebuild'>Start (no cache)</x-inputs.button>
@endif
<x-inputs.button isWarning x-on:click="deleteApplication = true">
<x-inputs.button isWarning x-on:click.prevent="deleteApplication = true">
Delete</x-inputs.button>
<span wire:poll.5000ms='pollingStatus'>
@if ($application->status === 'running')

View File

@@ -10,7 +10,7 @@
<x-inputs.button type="submit">
Update
</x-inputs.button>
<x-inputs.button x-on:click="deleteEnvironment = true" isWarning>
<x-inputs.button x-on:click.prevent="deleteEnvironment = true" isWarning>
Delete
</x-inputs.button>
</form>

View File

@@ -11,12 +11,13 @@
<x-inputs.input id="application.start_command" label="Start Command" />
<x-inputs.select id="application.build_pack" label="Build Pack" required>
<option value="nixpacks">Nixpacks</option>
<option value="docker">Docker</option>
<option disabled value="docker">Docker</option>
<option disabled value="compose">Compose</option>
</x-inputs.select>
@if ($application->settings->is_static)
<x-inputs.select id="application.static_image" label="Static Image" required>
<option value="nginx:alpine">nginx:alpine</option>
<option value="apache:alpine">apache:alpine</option>
<option disabled value="apache:alpine">apache:alpine</option>
</x-inputs.select>
@endif
</div>
@@ -42,15 +43,17 @@
Submit
</x-inputs.button>
</form>
<div class="flex flex-col pt-4 text-right w-52">
<x-inputs.input instantSave type="checkbox" id="is_static" label="Static website?" />
<x-inputs.input instantSave type="checkbox" id="is_auto_deploy" label="Auto Deploy?" />
<x-inputs.input instantSave type="checkbox" id="is_dual_cert" label="Dual Certs?" />
<x-inputs.input instantSave type="checkbox" id="is_previews" label="Previews?" />
<x-inputs.input instantSave type="checkbox" id="is_custom_ssl" label="Is Custom SSL?" />
<x-inputs.input instantSave type="checkbox" id="is_http2" label="Is Http2?" />
<x-inputs.input instantSave type="checkbox" id="is_git_submodules_allowed" label="Git Submodules Allowed?" />
<x-inputs.input instantSave type="checkbox" id="is_git_lfs_allowed" label="Git LFS Allowed?" />
<x-inputs.input instantSave type="checkbox" id="is_debug" label="Debug" />
<div class="flex flex-col pt-4">
<x-inputs.input noDirty instantSave type="checkbox" id="is_static" label="Static website?" />
<x-inputs.input noDirty instantSave type="checkbox" id="is_git_submodules_allowed"
label="Git Submodules Allowed?" />
<x-inputs.input noDirty instantSave type="checkbox" id="is_git_lfs_allowed" label="Git LFS Allowed?" />
<x-inputs.input noDirty instantSave type="checkbox" id="is_debug" label="Debug" />
<x-inputs.input noDirty instantSave type="checkbox" id="is_auto_deploy" label="Auto Deploy?" />
<x-inputs.input disabled instantSave type="checkbox" id="is_dual_cert" label="Dual Certs?" />
<x-inputs.input disabled instantSave type="checkbox" id="is_previews" label="Previews?" />
<x-inputs.input disabled instantSave type="checkbox" id="is_custom_ssl" label="Is Custom SSL?" />
<x-inputs.input disabled instantSave type="checkbox" id="is_http2" label="Is Http2?" />
</div>
</div>

View File

@@ -1,5 +1,5 @@
<div>
<a @if ($status === 'in_progress' || $status === 'holding') wire:poll='polling' @endif href="{{ url()->current() }}/{{ $deployment_uuid }}">
<a @if ($status === 'in_progress' || $status === 'queued') wire:poll='polling' @endif href="{{ url()->current() }}/{{ $deployment_uuid }}">
{{ $created_at }}
{{ $deployment_uuid }}</a>
{{ $status }}

View File

@@ -4,6 +4,12 @@
<div class="flex flex-col w-96">
<x-inputs.input id="application.git_repository" label="Git Repository" readonly />
<x-inputs.input id="application.git_branch" label="Git Branch" readonly />
<x-inputs.input id="application.git_commit_sha" label="Git Commit SHA" readonly />
<form wire:submit.prevent='submit'>
<x-inputs.input id="application.git_commit_sha" placeholder="HEAD" label="Git Commit SHA" />
<x-inputs.button type="submit">Save</x-inputs.button>
</form>
<a target="_blank" href="{{ $application->gitCommits }}">
<x-inputs.button>Commits ↗️</x-inputs.button>
</a>
</div>
</div>

View File

@@ -7,7 +7,7 @@
<x-inputs.button type="submit">
Update
</x-inputs.button>
<x-inputs.button x-on:click="deleteStorage = true" isWarning>
<x-inputs.button x-on:click.prevent="deleteStorage = true" isWarning>
Delete
</x-inputs.button>
</form>

View File

@@ -0,0 +1,13 @@
<div x-data="{ deleteProject: false }">
<x-naked-modal show="deleteProject" message='Are you sure you would like to delete this project?' />
@if ($resource_count > 0)
<x-inputs.button isWarning disabled="First delete all resources.">
Delete
</x-inputs.button>
@else
<x-inputs.button isWarning x-on:click.prevent="deleteProject = true">
Delete
</x-inputs.button>
@endif
</div>

View File

@@ -0,0 +1,31 @@
<div>
<div>
<h1>Select a private key</h1>
@foreach ($private_keys as $key)
@if ($private_key_id == $key->id)
<x-inputs.button class="bg-blue-500" wire:click.defer="setPrivateKey('{{ $key->id }}')">
{{ $key->name }}</x-inputs.button>
@else
<x-inputs.button wire:click.defer="setPrivateKey('{{ $key->id }}')">{{ $key->name }}
</x-inputs.button>
@endif
@endforeach
</div>
@isset($private_key_id)
<h1>Choose a repository</h1>
<form wire:submit.prevent='submit'>
<div class="flex items-end gap-2 pb-2">
<x-inputs.input class="w-96" id="repository_url" label="Repository URL" />
@if ($is_static)
<x-inputs.input id="publish_directory" label="Publish Directory" />
@else
<x-inputs.input type="number" id="port" label="Port" :readonly="$is_static" />
@endif
<x-inputs.input instantSave type="checkbox" id="is_static" label="Static Site?" />
</div>
<x-inputs.button type="submit">
Submit
</x-inputs.button>
</form>
@endisset
</div>

View File

@@ -0,0 +1,45 @@
<div>
@if ($github_apps->count() > 0)
<h1>Choose a GitHub App</h1>
@foreach ($github_apps as $ghapp)
<x-inputs.button wire:key="{{ $ghapp->id }}" wire:click="loadRepositories({{ $ghapp->id }})">
{{ $ghapp->name }}
</x-inputs.button>
@endforeach
<div>
@if ($repositories->count() > 0)
<h3>Choose a Repository</h3>
<select wire:model.defer="selected_repository_id">
@foreach ($repositories as $repo)
@if ($loop->first)
<option selected value="{{ data_get($repo, 'id') }}">{{ data_get($repo, 'name') }}</option>
@else
<option value="{{ data_get($repo, 'id') }}">{{ data_get($repo, 'name') }}</option>
@endif
@endforeach
</select>
<x-inputs.button wire:click="loadBranches">Select Repository</x-inputs.button>
@endif
</div>
<div>
@if ($branches->count() > 0)
<h3>Choose a Branch</h3>
<select wire:model.defer="selected_branch_name">
<option disabled>Choose a branch</option>
@foreach ($branches as $branch)
@if ($loop->first)
<option selected value="{{ data_get($branch, 'name') }}">{{ data_get($branch, 'name') }}
</option>
@else
<option value="{{ data_get($branch, 'name') }}">{{ data_get($branch, 'name') }}</option>
@endif
@endforeach
</select>
<x-inputs.button wire:click="submit">Save</x-inputs.button>
@endif
</div>
@else
Add new github app
@endif
</div>

View File

@@ -1,72 +1,17 @@
<div>
@if ($servers->count() > 0)
<h1>Choose a server</h1>
@endif
@forelse ($servers as $server)
@if ($chosenServer && $chosenServer['id'] === $server->id)
<x-inputs.button class="bg-blue-500" wire:click="chooseServer({{ $server }})">{{ $server->name }}
</x-inputs.button>
@else
<x-inputs.button wire:click="chooseServer({{ $server }})">{{ $server->name }}</x-inputs.button>
@endif
@empty
No servers found.
<p>Did you forget to add a destination on the server?</p>
@endforelse
@isset($chosenServer)
@if ($standalone_docker->count() > 0 || $swarm_docker->count() > 0)
<h1>Choose a destination</h1>
<div>
@foreach ($standalone_docker as $standalone)
@if ($chosenDestination?->uuid == $standalone->uuid)
<x-inputs.button class="bg-blue-500"
wire:click="setDestination('{{ $standalone->uuid }}','StandaloneDocker')">
{{ $standalone->network }}</x-inputs.button>
@else
<x-inputs.button wire:click="setDestination('{{ $standalone->uuid }}','StandaloneDocker')">
{{ $standalone->network }}</x-inputs.button>
@endif
@endforeach
@foreach ($swarm_docker as $standalone)
@if ($chosenDestination?->uuid == $standalone->uuid)
<x-inputs.button class="bg-blue-500"
wire:click="setDestination('{{ $standalone->uuid }}','SwarmDocker')">
{{ $standalone->network }}</x-inputs.button>
@else
<x-inputs.button wire:click="setDestination('{{ $standalone->uuid }}','SwarmDocker')">
{{ $standalone->uuid }}</x-inputs.button>
@endif
@endforeach
</div>
<div>
<a href="{{ route('destination.new', ['server_id' => $chosenServer['id']]) }}">Add
a new
destination</a>
</div>
@else
<h1>No destinations found on this server.</h1>
<a href="{{ route('destination.new', ['server_id' => $chosenServer['id']]) }}">Add
a
destination</a>
@endif
@endisset
@isset($chosenDestination)
<h1>Choose a repository</h1>
<form class="flex flex-col gap-2 w-96" wire:submit.prevent='submit'>
<x-inputs.input class="w-96" id="public_repository_url" label="Repository URL" />
<x-inputs.input instantSave type="checkbox" id="is_static" label="Static Site?" />
<h1>Choose a public repository</h1>
<form class="flex flex-col gap-2 w-96" wire:submit.prevent='submit'>
<x-inputs.input instantSave type="checkbox" id="is_static" label="Is it a static site?" />
<div class="flex gap-2">
<x-inputs.input class="w-96" id="repository_url" label="Repository URL" />
@if ($is_static)
<x-inputs.input id="publish_directory" label="Publish Directory" />
@else
<x-inputs.input type="number" id="port" label="Port" :readonly="$is_static" />
@endif
<x-inputs.button type="submit">
Submit
</x-inputs.button>
</form>
@endisset
</div>
<x-inputs.button type="submit">
Submit
</x-inputs.button>
</form>
</div>

View File

@@ -1,12 +1,18 @@
<div>
<form class="flex gap-2" wire:submit.prevent='runCommand'>
<x-inputs.input autofocus id="command" label="Command" required />
<form class="flex items-end justify-center gap-2" wire:submit.prevent='runCommand'>
<x-inputs.input class="w-[32rem]" autofocus noDirty noLabel id="command" label="Command" required />
<select wire:model.defer="server">
@foreach ($servers as $server)
<option value="{{ $server->uuid }}">{{ $server->name }}</option>
@if ($loop->first)
<option selected value="{{ $server->uuid }}">{{ $server->name }}</option>
@else
<option value="{{ $server->uuid }}">{{ $server->name }}</option>
@endif
@endforeach
</select>
<x-inputs.button type="submit">Run</x-inputs.button>
</form>
<livewire:activity-monitor />
<div class="container w-full pt-10 mx-auto">
<livewire:activity-monitor />
</div>
</div>

View File

@@ -18,15 +18,17 @@
@endif
</div>
</div>
<div>
<div class="flex">
<x-inputs.button type="submit">Submit</x-inputs.button>
<x-inputs.button wire:click.prevent='checkServer'>Check Server</x-inputs.button>
<x-inputs.button wire:click.prevent='installDocker'>Install Docker</x-inputs.button>
<x-inputs.button isWarning x-on:click="deleteServer = true">
<x-inputs.button isWarning x-on:click.prevent="deleteServer = true">
Delete
</x-inputs.button>
</div>
<x-inputs.input class="" disabled type="checkbox" id="server.settings.is_validated" label="Validated" />
</form>
@isset($uptime)
<p>Uptime: {{ $uptime }}</p>
@endisset

View File

@@ -1,31 +1,32 @@
<div>
<form class="flex flex-col" wire:submit.prevent='submit'>
<form class="flex flex-col gap-1" wire:submit.prevent='submit'>
<div class="flex items-center gap-2">
<h1>New Server</h1>
<x-inputs.button type="submit">
Save
</x-inputs.button>
</div>
<x-inputs.input id="name" label="Name" required />
<x-inputs.input id="description" label="Description" />
<x-inputs.input id="ip" label="IP Address" required />
<x-inputs.input id="user" label="User" />
<x-inputs.input type="number" id="port" label="Port" />
<x-inputs.input id="private_key_id" label="Private Key" required hidden />
<x-inputs.button class="mt-4" type="submit">
Submit
</x-inputs.button>
</form>
<div class="flex gap-4">
<div>
<x-inputs.input id="private_key_id" label="Private Key Id" readonly hidden />
@if ($private_keys->count() > 0)
<h1>Select a private key</h1>
@foreach ($private_keys as $key)
@if ($private_key_id == $key->id)
<x-inputs.button class="bg-blue-500" wire:click.defer="setPrivateKey('{{ $key->id }}')">
{{ $key->name }}</x-inputs.button>
@else
<x-inputs.button wire:click.defer="setPrivateKey('{{ $key->id }}')">{{ $key->name }}
</x-inputs.button>
@endif
<div class="box" :class="{ 'bg-coollabs': {{ $private_key_id === $key->id }} }"
wire:click.defer.prevent="setPrivateKey('{{ $key->id }}')">
{{ $key->name }}
</div>
@endforeach
</div>
<div>
<h2>Add a new One</h2>
<livewire:private-key.create />
</div>
</div>
@endif
</form>
@if ($private_keys->count() > 0)
<h2>Or add a new private key</h2>
@else
<h2>Create private key</h2>
@endif
<livewire:private-key.create />
</div>

View File

@@ -0,0 +1,14 @@
<div>
<form class="flex flex-col gap-2 w-96" wire:submit.prevent='createGitHubApp'>
<x-inputs.input id="name" label="Name" required />
<x-inputs.input id="html_url" label="HTML Url" required />
<x-inputs.input id="api_url" label="API Url" required />
<x-inputs.input id="organization" label="Organization" />
<x-inputs.input id="custom_user" label="Custom Git User" required />
<x-inputs.input id="custom_port" label="Custom Git Port" required />
<x-inputs.input type="checkbox" id="is_system_wide" label="System Wide" />
<x-inputs.button type="submit">
Submit
</x-inputs.button>
</form>
</div>

View File

@@ -0,0 +1,36 @@
<div x-data="{ deleteSource: false }">
<x-naked-modal show="deleteSource" message='Are you sure you would like to delete this source?' />
<h3>Change Github App</h3>
<form wire:submit.prevent='submit'>
<x-inputs.input id="github_app.name" label="App Name" required />
<x-inputs.input noDirty type="checkbox" label="System Wide?" instantSave id="is_system_wide" />
@if ($github_app->app_id)
<x-inputs.input id="github_app.organization" label="Organization" disabled
placeholder="Personal user if empty" />
@else
<x-inputs.input id="github_app.organization" label="Organization" placeholder="Personal user if empty" />
@endif
<x-inputs.input id="github_app.api_url" label="API Url" disabled />
<x-inputs.input id="github_app.html_url" label="HTML Url" disabled />
<x-inputs.input id="github_app.custom_user" label="User" required />
<x-inputs.input type="number" id="github_app.custom_port" label="Port" required />
@if ($github_app->app_id)
<x-inputs.input type="number" id="github_app.app_id" label="App Id" disabled />
<x-inputs.input type="number" id="github_app.installation_id" label="Installation Id" disabled />
<x-inputs.input id="github_app.client_id" label="Client Id" type="password" disabled />
<x-inputs.input id="github_app.client_secret" label="Client Secret" type="password" disabled />
<x-inputs.input id="github_app.webhook_secret" label="Webhook Secret" type="password" disabled />
<x-inputs.button type="submit">Save</x-inputs.button>
<x-inputs.button isWarning x-on:click.prevent="deleteSource = true">
Delete
</x-inputs.button>
@else
<div class="py-2">
<x-inputs.button type="submit">Save</x-inputs.button>
<x-inputs.button isWarning x-on:click.prevent="deleteSource = true">
Delete
</x-inputs.button>
</div>
@endif
</form>
</div>

View File

@@ -1,6 +1,6 @@
<x-layout>
<h1>Configuration</h1>
<x-applications.navbar :applicationId="$application->id" />
<x-applications.navbar :applicationId="$application->id" :gitBranchLocation="$application->gitBranchLocation" />
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }">
<div class="flex gap-4">
<a :class="activeTab === 'general' && 'text-purple-500'"

View File

@@ -1,5 +1,5 @@
<x-layout>
<h1>Deployment</h1>
<x-applications.navbar :applicationId="$application->id" />
<x-applications.navbar :applicationId="$application->id" :gitBranchLocation="$application->gitBranchLocation" />
<livewire:project.application.poll-deployment :activity="$activity" :deployment_uuid="$deployment_uuid" />
</x-layout>

View File

@@ -1,6 +1,6 @@
<x-layout>
<h1>Deployments</h1>
<x-applications.navbar :applicationId="$application->id" />
<x-applications.navbar :applicationId="$application->id" :gitBranchLocation="$application->gitBranchLocation" />
<div class="pt-2">
@forelse ($deployments as $deployment)
<livewire:project.application.get-deployments :deployment_uuid="data_get($deployment->properties, 'type_uuid')" :created_at="data_get($deployment, 'created_at')" :status="data_get($deployment->properties, 'status')" />

View File

@@ -1,24 +1,9 @@
<x-layout>
@if ($type === 'project')
<h1>New Project</h1>
@elseif ($type === 'resource')
<h1>New Resource</h1>
@if ($type === 'public')
<livewire:project.new.public-git-repository :type="$type" />
@elseif ($type === 'private-gh-app')
<livewire:project.new.github-private-repository :type="$type" />
@elseif ($type === 'private-deploy-key')
<livewire:project.new.github-private-repository-deploy-key :type="$type" />
@endif
<div x-data="{ activeTab: 'choose' }">
<div class="flex flex-col w-64 gap-2 mb-10">
<x-inputs.button @click.prevent="activeTab = 'public-repo'">Public Repository</x-inputs.button>
<x-inputs.button @click.prevent="activeTab = 'github-private-repo'">Private Repository (GitHub App)
</x-inputs.button>
@if ($type === 'project')
<livewire:project.new.empty-project />
@endif
</div>
<div x-cloak x-show="activeTab === 'public-repo'">
<livewire:project.new.public-git-repository :type="$type" />
</div>
<div x-cloak x-show="activeTab === 'github-private-repo'">
github-private-repo
</div>
</div>
</x-layout>

View File

@@ -1,8 +1,14 @@
<x-layout>
<h1>Resources <a href="{{ route('project.resources.new', Route::current()->parameters()) }}">
<div class="flex items-center gap-2">
<h1>Resources</h1>
<a href="{{ route('project.resources.new', Route::current()->parameters()) }}">
<x-inputs.button>New</x-inputs.button>
</a>
</h1>
<livewire:project.delete :project_id="$project->id" :resource_count="$project->applications->count()" />
</div>
@if ($environment->applications->count() === 0)
<p>No resources yet.</p>
@endif
<div>
@foreach ($environment->applications as $application)
<p>

View File

@@ -1,4 +1,3 @@
<x-layout>
<h1>New Server</h1>
<livewire:server.new.by-ip />
</x-layout>

View File

@@ -0,0 +1,67 @@
<x-layout>
<h1>GitHub App</h1>
<livewire:source.github.change :github_app="$github_app" />
@if (!$github_app->app_id)
<form x-data>
<x-inputs.button x-on:click.prevent="createGithubApp">Create GitHub Application</x-inputs.button>
</form>
<script>
function createGithubApp() {
const {
organization,
uuid,
html_url
} = @json($github_app);
let baseUrl = @js($host);
const name = @js($name);
const isDev = @js(config('app.env')) === 'local';
const devWebhook = @js(config('coolify.dev_webhook'));
if (isDev && devWebhook) {
baseUrl = devWebhook;
}
const webhookBaseUrl = `${baseUrl}/webhooks`;
const path = organization ? `organizations/${organization}/settings/apps/new` : 'settings/apps/new';
const data = {
name,
url: baseUrl,
hook_attributes: {
url: `${webhookBaseUrl}/source/github/events`,
active: true,
},
redirect_url: `${webhookBaseUrl}/source/github/redirect`,
callback_urls: [`${baseUrl}/login/github/app`],
public: false,
request_oauth_on_install: false,
setup_url: `${webhookBaseUrl}/source/github/install?source=${uuid}`,
setup_on_update: true,
default_permissions: {
contents: 'read',
metadata: 'read',
pull_requests: 'read',
emails: 'read'
},
default_events: ['pull_request', 'push']
};
const form = document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', `${html_url}/${path}?state=${uuid}`);
const input = document.createElement('input');
input.setAttribute('id', 'manifest');
input.setAttribute('name', 'manifest');
input.setAttribute('type', 'hidden');
input.setAttribute('value', JSON.stringify(data));
form.appendChild(input);
document.getElementsByTagName('body')[0].appendChild(form);
form.submit();
}
</script>
@elseif($github_app->app_id && !$github_app->installation_id)
<a href="{{ $installation_url }}">
<x-inputs.button>Install Repositories</x-inputs.button>
</a>
@elseif($github_app->app_id && $github_app->installation_id)
<a href="{{ $installation_url }}">
<x-inputs.button>Update Repositories</x-inputs.button>
</a>
@endif
</x-layout>

View File

@@ -0,0 +1,4 @@
<x-layout>
<h1>New Git App</h1>
<livewire:source.create />
</x-layout>