feat: tags and tag deploy webhooks
This commit is contained in:
parent
44efe0b5e1
commit
6312c0ba84
@ -29,7 +29,7 @@ public function mount()
|
|||||||
}
|
}
|
||||||
$this->project = $project;
|
$this->project = $project;
|
||||||
$this->environment = $environment;
|
$this->environment = $environment;
|
||||||
$this->applications = $environment->applications->sortBy('name');
|
$this->applications = $environment->applications->load(['tags'])->sortBy('name');
|
||||||
$this->applications = $this->applications->map(function ($application) {
|
$this->applications = $this->applications->map(function ($application) {
|
||||||
if (data_get($application, 'environment.project.uuid')) {
|
if (data_get($application, 'environment.project.uuid')) {
|
||||||
$application->hrefLink = route('project.application.configuration', [
|
$application->hrefLink = route('project.application.configuration', [
|
||||||
|
62
app/Livewire/Project/Shared/Tags.php
Normal file
62
app/Livewire/Project/Shared/Tags.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Tags extends Component
|
||||||
|
{
|
||||||
|
public $resource = null;
|
||||||
|
public ?string $new_tag = null;
|
||||||
|
protected $listeners = [
|
||||||
|
'refresh' => '$refresh',
|
||||||
|
];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public function deleteTag($id, $name)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$found_more_tags = Tag::where(['name' => $name, 'team_id' => currentTeam()->id])->first();
|
||||||
|
$this->resource->tags()->detach($id);
|
||||||
|
if ($found_more_tags->resources()->get()->count() == 0) {
|
||||||
|
$found_more_tags->delete();
|
||||||
|
}
|
||||||
|
$this->refresh();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function refresh()
|
||||||
|
{
|
||||||
|
$this->resource->load(['tags']);
|
||||||
|
$this->new_tag = null;
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate([
|
||||||
|
'new_tag' => 'required|string|min:2'
|
||||||
|
]);
|
||||||
|
$tags = str($this->new_tag)->trim()->explode(' ');
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first();
|
||||||
|
if (!$found) {
|
||||||
|
$found = Tag::create([
|
||||||
|
'name' => $tag,
|
||||||
|
'team_id' => currentTeam()->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->resource->tags()->syncWithoutDetaching($found->id);
|
||||||
|
}
|
||||||
|
$this->refresh();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.shared.tags');
|
||||||
|
}
|
||||||
|
}
|
18
app/Livewire/Tags/Index.php
Normal file
18
app/Livewire/Tags/Index.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Tags;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Index extends Component
|
||||||
|
{
|
||||||
|
public $tags = [];
|
||||||
|
public function mount() {
|
||||||
|
$this->tags = Tag::where('team_id', currentTeam()->id)->get()->unique('name')->sortBy('name');
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.tags.index');
|
||||||
|
}
|
||||||
|
}
|
27
app/Livewire/Tags/Show.php
Normal file
27
app/Livewire/Tags/Show.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Tags;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Show extends Component
|
||||||
|
{
|
||||||
|
public Tag $tag;
|
||||||
|
public $resources;
|
||||||
|
public $webhook = null;
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$tag = Tag::ownedByCurrentTeam()->where('name', request()->tag_name)->first();
|
||||||
|
if (!$tag) {
|
||||||
|
return redirect()->route('tags.index');
|
||||||
|
}
|
||||||
|
$this->webhook = generatTagDeployWebhook($tag->name);
|
||||||
|
$this->resources = $tag->resources()->get();
|
||||||
|
$this->tag = $tag;
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.tags.show');
|
||||||
|
}
|
||||||
|
}
|
@ -211,6 +211,10 @@ public function portsExposesArray(): Attribute
|
|||||||
: explode(',', $this->ports_exposes)
|
: explode(',', $this->ports_exposes)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
32
app/Models/Tag.php
Normal file
32
app/Models/Tag.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
|
||||||
|
class Tag extends BaseModel
|
||||||
|
{
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
|
||||||
|
public function name(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn ($value) => strtolower($value),
|
||||||
|
set: fn ($value) => strtolower($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
static public function ownedByCurrentTeam()
|
||||||
|
{
|
||||||
|
return Tag::whereTeamId(currentTeam()->id)->orderBy('name');
|
||||||
|
}
|
||||||
|
public function applications()
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Application::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resources() {
|
||||||
|
return $this->applications();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -110,9 +110,9 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
|
|||||||
}
|
}
|
||||||
if ($error instanceof UniqueConstraintViolationException) {
|
if ($error instanceof UniqueConstraintViolationException) {
|
||||||
if (isset($livewire)) {
|
if (isset($livewire)) {
|
||||||
return $livewire->dispatch('error', "A resource with the same name already exists.");
|
return $livewire->dispatch('error', "Duplicate entry found.","Please use a different name.");
|
||||||
}
|
}
|
||||||
return "A resource with the same name already exists.";
|
return "Duplicate entry found. Please use a different name.";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($error instanceof Throwable) {
|
if ($error instanceof Throwable) {
|
||||||
@ -481,7 +481,14 @@ function queryResourcesByUuid(string $uuid)
|
|||||||
if ($mariadb) return $mariadb;
|
if ($mariadb) return $mariadb;
|
||||||
return $resource;
|
return $resource;
|
||||||
}
|
}
|
||||||
|
function generatTagDeployWebhook($tag_name)
|
||||||
|
{
|
||||||
|
$baseUrl = base_url();
|
||||||
|
$api = Url::fromString($baseUrl) . '/api/v1';
|
||||||
|
$endpoint = "/deploy/tag/$tag_name";
|
||||||
|
$url = $api . $endpoint . "?force=false";
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
function generateDeployWebhook($resource)
|
function generateDeployWebhook($resource)
|
||||||
{
|
{
|
||||||
$baseUrl = base_url();
|
$baseUrl = base_url();
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an additional second for every 100th word of the toast messages.
|
|
||||||
*
|
|
||||||
* Supported: true | false
|
|
||||||
*/
|
|
||||||
'accessibility' => true,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The vertical alignment of the toast container.
|
|
||||||
*
|
|
||||||
* Supported: "bottom", "middle" or "top"
|
|
||||||
*/
|
|
||||||
'alignment' => 'top',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allow users to close toast messages prematurely.
|
|
||||||
*
|
|
||||||
* Supported: true | false
|
|
||||||
*/
|
|
||||||
'closeable' => true,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The on-screen duration of each toast.
|
|
||||||
*
|
|
||||||
* Minimum: 3000 (in milliseconds)
|
|
||||||
*/
|
|
||||||
'duration' => 5000,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The horizontal position of each toast.
|
|
||||||
*
|
|
||||||
* Supported: "center", "left" or "right"
|
|
||||||
*/
|
|
||||||
'position' => 'center',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether messages passed as translation keys should be translated automatically.
|
|
||||||
*
|
|
||||||
* Supported: true | false
|
|
||||||
*/
|
|
||||||
'translate' => true,
|
|
||||||
];
|
|
39
database/migrations/2024_02_01_111228_create_tags_table.php
Normal file
39
database/migrations/2024_02_01_111228_create_tags_table.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('tags', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('uuid')->unique();
|
||||||
|
$table->string('name')->unique();
|
||||||
|
$table->foreignId('team_id')->nullable()->constrained()->onDelete('cascade');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
Schema::create('taggables', function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('tag_id');
|
||||||
|
$table->unsignedBigInteger('taggable_id');
|
||||||
|
$table->string('taggable_type');
|
||||||
|
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
|
||||||
|
$table->unique(['tag_id', 'taggable_id', 'taggable_type'], 'taggable_unique'); // Composite unique index
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('taggables');
|
||||||
|
Schema::dropIfExists('tags');
|
||||||
|
}
|
||||||
|
};
|
@ -76,7 +76,7 @@ .box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.box-without-bg {
|
.box-without-bg {
|
||||||
@apply flex p-2 transition-colors min-h-full hover:text-white hover:no-underline min-h-[4rem];
|
@apply flex p-2 transition-colors hover:text-white hover:no-underline min-h-[4rem];
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
|
@ -83,6 +83,18 @@ class="{{ request()->is('command-center') ? 'text-warning icon' : 'icon' }}" vie
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li title="Tags">
|
||||||
|
<a class="hover:bg-transparent" href="{{ route('tags.index') }}">
|
||||||
|
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path
|
||||||
|
d="M3 8v4.172a2 2 0 0 0 .586 1.414l5.71 5.71a2.41 2.41 0 0 0 3.408 0l3.592-3.592a2.41 2.41 0 0 0 0-3.408l-5.71-5.71A2 2 0 0 0 9.172 6H5a2 2 0 0 0-2 2" />
|
||||||
|
<path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
@if (isInstanceAdmin() && !isCloud())
|
@if (isInstanceAdmin() && !isCloud())
|
||||||
|
@ -22,20 +22,7 @@
|
|||||||
if (typeof options.html != 'undefined') html = options.html;
|
if (typeof options.html != 'undefined') html = options.html;
|
||||||
|
|
||||||
window.dispatchEvent(new CustomEvent('toast-show', { detail: { type: type, message: message, description: description, position: position, html: html } }));
|
window.dispatchEvent(new CustomEvent('toast-show', { detail: { type: type, message: message, description: description, position: position, html: html } }));
|
||||||
}
|
}" class="relative space-y-5">
|
||||||
|
|
||||||
window.customToastHTML = `
|
|
||||||
<div class='relative flex items-start justify-center p-4'>
|
|
||||||
<div class='flex flex-col'>
|
|
||||||
<p class='text-sm font-medium text-gray-800'>New Friend Request</p>
|
|
||||||
<p class='mt-1 text-xs leading-none text-gray-800'>Friend request from John Doe.</p>
|
|
||||||
<div class='flex mt-3'>
|
|
||||||
<button type='button' @click='burnToast(toast.id)' class='inline-flex items-center px-2 py-1 text-xs font-semibold text-white bg-indigo-600 rounded shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600'>Accept</button>
|
|
||||||
<button type='button' @click='burnToast(toast.id)' class='inline-flex items-center px-2 py-1 ml-3 text-xs font-semibold text-gray-900 bg-white rounded shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50'>Decline</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`" class="relative space-y-5">
|
|
||||||
<template x-teleport="body">
|
<template x-teleport="body">
|
||||||
<ul x-data="{
|
<ul x-data="{
|
||||||
toasts: [],
|
toasts: [],
|
||||||
|
@ -63,6 +63,9 @@
|
|||||||
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
|
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
|
||||||
href="#">Resource Operations
|
href="#">Resource Operations
|
||||||
</a>
|
</a>
|
||||||
|
<a :class="activeTab === 'tags' && 'text-white'"
|
||||||
|
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
|
||||||
|
</a>
|
||||||
<a :class="activeTab === 'danger' && 'text-white'"
|
<a :class="activeTab === 'danger' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger Zone
|
@click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger Zone
|
||||||
</a>
|
</a>
|
||||||
@ -112,6 +115,9 @@
|
|||||||
<div x-cloak x-show="activeTab === 'resource-operations'">
|
<div x-cloak x-show="activeTab === 'resource-operations'">
|
||||||
<livewire:project.shared.resource-operations :resource="$application" />
|
<livewire:project.shared.resource-operations :resource="$application" />
|
||||||
</div>
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'tags'">
|
||||||
|
<livewire:project.shared.tags :resource="$application" />
|
||||||
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'danger'">
|
<div x-cloak x-show="activeTab === 'danger'">
|
||||||
<livewire:project.shared.danger :resource="$application" />
|
<livewire:project.shared.danger :resource="$application" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -112,9 +112,7 @@ class="underline" href="https://coolify.io/docs/docker/registry"
|
|||||||
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
|
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
|
||||||
label="Docker Image Tag" />
|
label="Docker Image Tag" />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@ -46,9 +46,10 @@ class="items-center justify-center box">+ Add New Resource</a>
|
|||||||
@else
|
@else
|
||||||
<div x-data="searchComponent()">
|
<div x-data="searchComponent()">
|
||||||
<x-forms.input placeholder="Search for name, fqdn..." class="w-full" x-model="search" />
|
<x-forms.input placeholder="Search for name, fqdn..." class="w-full" x-model="search" />
|
||||||
<div class="grid gap-2 pt-4 lg:grid-cols-2">
|
<div class="grid gap-4 pt-4 lg:grid-cols-4">
|
||||||
<template x-for="item in filteredApplications" :key="item.id">
|
<template x-for="item in filteredApplications" :key="item.id">
|
||||||
<a class="relative box group" :href="item.hrefLink">
|
<span class="relative">
|
||||||
|
<a class="h-24 box group" :href="item.hrefLink">
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="pb-2 font-bold text-white" x-text="item.name"></div>
|
<div class="pb-2 font-bold text-white" x-text="item.name"></div>
|
||||||
<div class="description" x-text="item.description"></div>
|
<div class="description" x-text="item.description"></div>
|
||||||
@ -64,6 +65,15 @@ class="items-center justify-center box">+ Add New Resource</a>
|
|||||||
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
||||||
</template>
|
</template>
|
||||||
</a>
|
</a>
|
||||||
|
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
|
||||||
|
<template x-for="tag in item.tags">
|
||||||
|
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
|
||||||
|
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
|
||||||
|
</template>
|
||||||
|
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
|
||||||
|
@click.prevent="goto(item)">Add tag</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template x-for="item in filteredPostgresqls" :key="item.id">
|
<template x-for="item in filteredPostgresqls" :key="item.id">
|
||||||
<a class="relative box group" :href="item.hrefLink">
|
<a class="relative box group" :href="item.hrefLink">
|
||||||
@ -184,6 +194,13 @@ function searchComponent() {
|
|||||||
mysqls: @js($mysqls),
|
mysqls: @js($mysqls),
|
||||||
mariadbs: @js($mariadbs),
|
mariadbs: @js($mariadbs),
|
||||||
services: @js($services),
|
services: @js($services),
|
||||||
|
gotoTag(tag) {
|
||||||
|
window.location.href = '/tags/' + tag;
|
||||||
|
},
|
||||||
|
goto(item) {
|
||||||
|
const hrefLink = item.hrefLink;
|
||||||
|
window.location.href = `${hrefLink}#tags`;
|
||||||
|
},
|
||||||
get filteredApplications() {
|
get filteredApplications() {
|
||||||
if (this.search === '') {
|
if (this.search === '') {
|
||||||
return this.applications;
|
return this.applications;
|
||||||
|
13
resources/views/livewire/project/shared/tags.blade.php
Normal file
13
resources/views/livewire/project/shared/tags.blade.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<div>
|
||||||
|
<h2>Tags</h2>
|
||||||
|
@foreach ($this->resource->tags as $tag)
|
||||||
|
<div>
|
||||||
|
<div>{{ $tag->name }}</div>
|
||||||
|
<x-forms.button isError wire:click="deleteTag('{{ $tag->id }}','{{ $tag->name }}')">Delete</x-forms.button>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
<form wire:submit='submit'>
|
||||||
|
<x-forms.input label="Add/Assign a tag" wire:model="new_tag" wire:confirm="Are you sure you want to delete this post?" />
|
||||||
|
<x-forms.button type="submit">Add</x-forms.button>
|
||||||
|
</form>
|
||||||
|
</div>
|
10
resources/views/livewire/tags/index.blade.php
Normal file
10
resources/views/livewire/tags/index.blade.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<div>
|
||||||
|
<h1>Tags</h1>
|
||||||
|
<div class="flex gap-2 pt-10">
|
||||||
|
@forelse ($tags as $tag)
|
||||||
|
<a class="box" href="{{ route('tags.show', ['tag_name' => $tag->name]) }}">{{ $tag->name }}</a>
|
||||||
|
@empty
|
||||||
|
<p>No tags yet</p>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
</div>
|
18
resources/views/livewire/tags/show.blade.php
Normal file
18
resources/views/livewire/tags/show.blade.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<div>
|
||||||
|
<h1>Tag: {{ $tag->name }}</h1>
|
||||||
|
<div class="">Tag details</div>
|
||||||
|
<div class="lg:w-[500px] pt-4">
|
||||||
|
<x-forms.input readonly label="Tag Deploy Webhook URL" id="webhook" />
|
||||||
|
</div>
|
||||||
|
<div class="pt-4">
|
||||||
|
<div class="flex items-end gap-2">
|
||||||
|
<h3>Resources</h3>
|
||||||
|
<x-forms.button>Redeploy All</x-forms.button>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-2 pt-4 lg:grid-cols-2">
|
||||||
|
@foreach ($resources as $resource)
|
||||||
|
<div class="box">{{ data_get($resource, 'name') }}</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -7,6 +7,7 @@
|
|||||||
use App\Actions\Database\StartRedis;
|
use App\Actions\Database\StartRedis;
|
||||||
use App\Actions\Service\StartService;
|
use App\Actions\Service\StartService;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Tag;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Providers\RouteServiceProvider;
|
use App\Providers\RouteServiceProvider;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -36,12 +37,12 @@
|
|||||||
'middleware' => $middlewares,
|
'middleware' => $middlewares,
|
||||||
'prefix' => 'v1'
|
'prefix' => 'v1'
|
||||||
], function () {
|
], function () {
|
||||||
Route::get('/deployments', function() {
|
Route::get('/deployments', function () {
|
||||||
return ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->get([
|
return ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->get([
|
||||||
"id",
|
"id",
|
||||||
"server_id",
|
"server_id",
|
||||||
"status"
|
"status"
|
||||||
])->groupBy("server_id")->map(function($item) {
|
])->groupBy("server_id")->map(function ($item) {
|
||||||
return $item;
|
return $item;
|
||||||
})->toArray();
|
})->toArray();
|
||||||
});
|
});
|
||||||
@ -131,6 +132,92 @@
|
|||||||
}
|
}
|
||||||
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
});
|
});
|
||||||
|
Route::get('/deploy/tag/{tag_name}', function (Request $request) {
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$team_id = data_get($token, 'team_id');
|
||||||
|
$tag_name = $request->route('tag_name');
|
||||||
|
$force = $request->query->get('force') ?? false;
|
||||||
|
if (is_null($team_id)) {
|
||||||
|
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = collect([]);
|
||||||
|
$tag = Tag::where(['name' => $tag_name, 'team_id' => $team_id])->first();
|
||||||
|
if (!$tag) {
|
||||||
|
return response()->json(['error' => 'Tag not found.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
|
}
|
||||||
|
$resources = $tag->resources()->get();
|
||||||
|
if ($resources->count() === 0) {
|
||||||
|
return response()->json(['error' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
|
}
|
||||||
|
foreach ($resources as $resource) {
|
||||||
|
if ($resource) {
|
||||||
|
$type = $resource->getMorphClass();
|
||||||
|
if ($type === 'App\Models\Application') {
|
||||||
|
queue_application_deployment(
|
||||||
|
application: $resource,
|
||||||
|
deployment_uuid: new Cuid2(7),
|
||||||
|
force_rebuild: $force,
|
||||||
|
);
|
||||||
|
$message->push("Application {$resource->name} deployment queued.");
|
||||||
|
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartPostgresql::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneRedis') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartRedis::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMongodb::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMysql::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMariadb::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\Service') {
|
||||||
|
StartService::run($resource);
|
||||||
|
$message->push("Service {$resource->name} started. It could take a while, be patient.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ray($resources);
|
||||||
|
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
return response()->json(['message' => $message->toArray()], 200);
|
||||||
|
}
|
||||||
|
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::middleware(['throttle:5'])->group(function () {
|
Route::middleware(['throttle:5'])->group(function () {
|
||||||
|
@ -67,6 +67,10 @@
|
|||||||
|
|
||||||
use App\Livewire\Source\Github\Change as GitHubChange;
|
use App\Livewire\Source\Github\Change as GitHubChange;
|
||||||
use App\Livewire\Subscription\Index as SubscriptionIndex;
|
use App\Livewire\Subscription\Index as SubscriptionIndex;
|
||||||
|
|
||||||
|
use App\Livewire\Tags\Index as TagsIndex;
|
||||||
|
use App\Livewire\Tags\Show as TagsShow;
|
||||||
|
|
||||||
use App\Livewire\TeamSharedVariablesIndex;
|
use App\Livewire\TeamSharedVariablesIndex;
|
||||||
use App\Livewire\Waitlist\Index as WaitlistIndex;
|
use App\Livewire\Waitlist\Index as WaitlistIndex;
|
||||||
|
|
||||||
@ -106,7 +110,10 @@
|
|||||||
Route::get('/settings/license', SettingsLicense::class)->name('settings.license');
|
Route::get('/settings/license', SettingsLicense::class)->name('settings.license');
|
||||||
|
|
||||||
Route::get('/profile', ProfileIndex::class)->name('profile');
|
Route::get('/profile', ProfileIndex::class)->name('profile');
|
||||||
|
Route::prefix('tags')->group(function () {
|
||||||
|
Route::get('/', TagsIndex::class)->name('tags.index');
|
||||||
|
Route::get('/{tag_name}', TagsShow::class)->name('tags.show');
|
||||||
|
});
|
||||||
Route::prefix('team')->group(function () {
|
Route::prefix('team')->group(function () {
|
||||||
Route::get('/', TeamIndex::class)->name('team.index');
|
Route::get('/', TeamIndex::class)->name('team.index');
|
||||||
Route::get('/new', TeamCreate::class)->name('team.create');
|
Route::get('/new', TeamCreate::class)->name('team.create');
|
||||||
|
Loading…
Reference in New Issue
Block a user