wip
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<x-layout>
|
||||
<livewire:run-command />
|
||||
<livewire:run-command :servers="$servers" />
|
||||
</x-layout>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
541
resources/views/components/magic-bar.blade.php
Normal file
541
resources/views/components/magic-bar.blade.php
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
13
resources/views/livewire/project/delete.blade.php
Normal file
13
resources/views/livewire/project/delete.blade.php
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
14
resources/views/livewire/source/create.blade.php
Normal file
14
resources/views/livewire/source/create.blade.php
Normal 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>
|
||||
36
resources/views/livewire/source/github/change.blade.php
Normal file
36
resources/views/livewire/source/github/change.blade.php
Normal 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>
|
||||
@@ -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'"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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')" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<x-layout>
|
||||
<h1>New Server</h1>
|
||||
<livewire:server.new.by-ip />
|
||||
</x-layout>
|
||||
|
||||
67
resources/views/source/github/show.blade.php
Normal file
67
resources/views/source/github/show.blade.php
Normal 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>
|
||||
4
resources/views/source/new.blade.php
Normal file
4
resources/views/source/new.blade.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<x-layout>
|
||||
<h1>New Git App</h1>
|
||||
<livewire:source.create />
|
||||
</x-layout>
|
||||
Reference in New Issue
Block a user