Merge pull request #1702 from coollabsio/next

v4.0.0-beta.206
This commit is contained in:
Andras Bacsai 2024-02-05 10:06:59 +01:00 committed by GitHub
commit 6e3dc474f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 222 additions and 123 deletions

View File

@ -73,12 +73,17 @@ public function by_tags(string $tags, int $team_id, bool $force = false)
$message->push("Tag {$tag} not found.");
continue;
}
$resources = $found_tag->resources()->get();
if ($resources->count() === 0) {
$applications = $found_tag->applications();
$services = $found_tag->services();
if ($applications->count() === 0 && $services->count() === 0) {
$message->push("No resources found for tag {$tag}.");
continue;
}
foreach ($resources as $resource) {
foreach ($applications as $resource) {
$return_message = $this->deploy_resource($resource, $force);
$message = $message->merge($return_message);
}
foreach ($services as $resource) {
$return_message = $this->deploy_resource($resource, $force);
$message = $message->merge($return_message);
}

View File

@ -166,10 +166,6 @@ public function __construct(int $application_deployment_queue_id)
public function handle(): void
{
$this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
// Generate custom host<->ip mapping
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
if (!is_null($allContainers)) {

View File

@ -22,12 +22,12 @@ class CloneMe extends Component
public ?int $selectedDestination = null;
public ?Server $server = null;
public $resources = [];
public string $newProjectName = '';
public string $newName = '';
protected $messages = [
'selectedServer' => 'Please select a server.',
'selectedDestination' => 'Please select a server & destination.',
'newProjectName' => 'Please enter a name for the new project.',
'newName' => 'Please enter a name for the new project or environment.',
];
public function mount($project_uuid)
{
@ -36,7 +36,7 @@ public function mount($project_uuid)
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
$this->project_id = $this->project->id;
$this->servers = currentTeam()->servers;
$this->newProjectName = str($this->project->name . '-clone-' . (string)new Cuid2(7))->slug();
$this->newName = str($this->project->name . '-clone-' . (string)new Cuid2(7))->slug();
}
public function render()
@ -46,34 +46,50 @@ public function render()
public function selectServer($server_id, $destination_id)
{
if ($server_id == $this->selectedServer && $destination_id == $this->selectedDestination) {
$this->selectedServer = null;
$this->selectedDestination = null;
$this->server = null;
return;
}
$this->selectedServer = $server_id;
$this->selectedDestination = $destination_id;
$this->server = $this->servers->where('id', $server_id)->first();
}
public function clone()
public function clone(string $type)
{
try {
$this->validate([
'selectedDestination' => 'required',
'newProjectName' => 'required',
'newName' => 'required',
]);
$foundProject = Project::where('name', $this->newProjectName)->first();
if ($foundProject) {
throw new \Exception('Project with the same name already exists.');
}
$newProject = Project::create([
'name' => $this->newProjectName,
'team_id' => currentTeam()->id,
'description' => $this->project->description . ' (clone)',
]);
if ($this->environment->name !== 'production') {
$newProject->environments()->create([
'name' => $this->environment->name,
if ($type === 'project') {
$foundProject = Project::where('name', $this->newName)->first();
if ($foundProject) {
throw new \Exception('Project with the same name already exists.');
}
$project = Project::create([
'name' => $this->newName,
'team_id' => currentTeam()->id,
'description' => $this->project->description . ' (clone)',
]);
if ($this->environment->name !== 'production') {
$project->environments()->create([
'name' => $this->environment->name,
]);
}
$environment = $project->environments->where('name', $this->environment->name)->first();
} else {
$foundEnv = $this->project->environments()->where('name', $this->newName)->first();
if ($foundEnv) {
throw new \Exception('Environment with the same name already exists.');
}
$project = $this->project;
$environment = $this->project->environments()->create([
'name' => $this->newName,
]);
}
$newEnvironment = $newProject->environments->where('name', $this->environment->name)->first();
// Clone Applications
$applications = $this->environment->applications;
$databases = $this->environment->databases();
$services = $this->environment->services;
@ -83,7 +99,7 @@ public function clone()
'uuid' => $uuid,
'fqdn' => generateFqdn($this->server, $uuid),
'status' => 'exited',
'environment_id' => $newEnvironment->id,
'environment_id' => $environment->id,
// This is not correct, but we need to set it to something
'destination_id' => $this->selectedDestination,
]);
@ -110,7 +126,7 @@ public function clone()
'uuid' => $uuid,
'status' => 'exited',
'started_at' => null,
'environment_id' => $newEnvironment->id,
'environment_id' => $environment->id,
'destination_id' => $this->selectedDestination,
]);
$newDatabase->save();
@ -136,7 +152,7 @@ public function clone()
$uuid = (string)new Cuid2(7);
$newService = $service->replicate()->fill([
'uuid' => $uuid,
'environment_id' => $newEnvironment->id,
'environment_id' => $environment->id,
'destination_id' => $this->selectedDestination,
]);
$newService->save();
@ -153,8 +169,8 @@ public function clone()
$newService->parse();
}
return redirect()->route('project.resource.index', [
'project_uuid' => $newProject->uuid,
'environment_name' => $newEnvironment->name,
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
]);
} catch (\Exception $e) {
return handleError($e, $this);

View File

@ -9,6 +9,7 @@
use App\Models\Project;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Support\Collection;
use Livewire\Component;
use Spatie\Url\Url;
use Illuminate\Support\Str;
@ -18,7 +19,7 @@ class GithubPrivateRepositoryDeployKey extends Component
public $current_step = 'private_keys';
public $parameters;
public $query;
public $private_keys;
public $private_keys =[];
public int $private_key_id;
public int $port = 3000;
@ -33,6 +34,11 @@ class GithubPrivateRepositoryDeployKey extends Component
public $build_pack = 'nixpacks';
public bool $show_is_static = true;
private object $repository_url_parsed;
private GithubApp|GitlabApp|string $git_source = 'other';
private ?string $git_host = null;
private string $git_repository;
protected $rules = [
'repository_url' => 'required',
'branch' => 'required|string',
@ -49,10 +55,7 @@ class GithubPrivateRepositoryDeployKey extends Component
'publish_directory' => 'Publish directory',
'build_pack' => 'Build pack',
];
private object $repository_url_parsed;
private GithubApp|GitlabApp|string $git_source = 'other';
private ?string $git_host = null;
private string $git_repository;
public function mount()
{

View File

@ -37,12 +37,13 @@ public function addTag(string $id, string $name)
return handleError($e, $this);
}
}
public function deleteTag($id, $name)
public function deleteTag(string $id)
{
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 = Tag::where(['id' => $id, 'team_id' => currentTeam()->id])->first();
if ($found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0){
$found_more_tags->delete();
}
$this->refresh();
@ -53,6 +54,7 @@ public function deleteTag($id, $name)
public function refresh()
{
$this->resource->load(['tags']);
$this->tags = Tag::ownedByCurrentTeam()->get();
$this->new_tag = null;
}
public function submit()

View File

@ -39,7 +39,7 @@ public function checkConnection()
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
} else {
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/configuration#openssh-server">documentation</a> for further help.');
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh#openssh">documentation</a> for further help.');
return;
}
} catch (\Throwable $e) {

View File

@ -9,15 +9,30 @@
class Show extends Component
{
public $tags;
public Tag $tag;
public $resources;
public $applications;
public $services;
public $webhook = null;
public $deployments_per_tag_per_server = [];
public function mount()
{
$this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
$tag = $this->tags->where('name', request()->tag_name)->first();
if (!$tag) {
return redirect()->route('tags.index');
}
$this->webhook = generatTagDeployWebhook($tag->name);
$this->applications = $tag->applications()->get();
$this->services = $tag->services()->get();
$this->tag = $tag;
$this->get_deployments();
}
public function get_deployments()
{
try {
$resource_ids = $this->resources->pluck('id');
$resource_ids = $this->applications->pluck('id');
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $resource_ids)->get([
"id",
"application_id",
@ -35,7 +50,11 @@ public function get_deployments()
public function redeploy_all()
{
try {
$this->resources->each(function ($resource) {
$this->applications->each(function ($resource) {
$deploy = new Deploy();
$deploy->deploy_resource($resource);
});
$this->services->each(function ($resource) {
$deploy = new Deploy();
$deploy->deploy_resource($resource);
});
@ -44,17 +63,7 @@ public function redeploy_all()
return handleError($e, $this);
}
}
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;
$this->get_deployments();
}
public function render()
{
return view('livewire.tags.show');

View File

@ -216,6 +216,9 @@ public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
public function project() {
return data_get($this, 'environment.project');
}
public function team()
{
return data_get($this, 'environment.project.team');

View File

@ -16,6 +16,10 @@ public function type()
{
return 'service';
}
public function project()
{
return data_get($this, 'environment.project');
}
public function team()
{
return data_get($this, 'environment.project.team');

View File

@ -24,9 +24,8 @@ public function applications()
{
return $this->morphedByMany(Application::class, 'taggable');
}
public function resources() {
return $this->applications();
public function services()
{
return $this->morphedByMany(Service::class, 'taggable');
}
}

View File

@ -1,5 +1,6 @@
<?php
use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\ApplicationDeploymentJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
@ -29,6 +30,9 @@ function queue_application_deployment(Application $application, string $deployme
]);
if (next_queuable($server_id, $application_id)) {
$deployment->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
@ -40,6 +44,9 @@ function queue_next_deployment(Application $application)
$server_id = $application->destination->server_id;
$next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first();
if ($next_found) {
$next_found->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $next_found->id,
));

View File

@ -7,7 +7,7 @@
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.205',
'release' => '4.0.0-beta.206',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.205';
return '4.0.0-beta.206';

View File

@ -76,7 +76,7 @@ .icon:hover {
}
.box {
@apply flex p-2 transition-colors cursor-pointer min-h-[4rem] bg-coolgray-100 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
@apply flex p-2 transition-colors cursor-pointer min-h-[4rem] bg-coolgray-100 hover:bg-coollabs-100 hover:text-white hover:no-underline;
}
.box-without-bg {

View File

@ -4,15 +4,22 @@
'isErrorButton' => false,
'disabled' => false,
'action' => 'delete',
'content' => null,
])
<div x-data="{ modalOpen: false }" @keydown.escape.window="modalOpen = false" :class="{ 'z-40': modalOpen }"
class="relative w-auto h-auto">
@if ($disabled)
<x-forms.button isError disabled>{{ $buttonTitle }}</x-forms.button>
@elseif ($isErrorButton)
<x-forms.button isError @click="modalOpen=true">{{ $buttonTitle }}</x-forms.button>
@if ($content)
<div @click="modalOpen=true">
{{ $content }}
</div>
@else
<x-forms.button @click="modalOpen=true">{{ $buttonTitle }}</x-forms.button>
@if ($disabled)
<x-forms.button isError disabled>{{ $buttonTitle }}</x-forms.button>
@elseif ($isErrorButton)
<x-forms.button isError @click="modalOpen=true">{{ $buttonTitle }}</x-forms.button>
@else
<x-forms.button @click="modalOpen=true">{{ $buttonTitle }}</x-forms.button>
@endif
@endif
<template x-teleport="body">
<div x-show="modalOpen" class="fixed top-0 left-0 z-[99] flex items-center justify-center w-screen h-screen"
@ -30,13 +37,13 @@ class="absolute inset-0 w-full h-full bg-black bg-opacity-20 backdrop-blur-sm"><
class="relative w-full py-6 border rounded shadow-lg bg-coolgray-100 px-7 border-coolgray-300 sm:max-w-lg">
<div class="flex items-center justify-between pb-3">
<h3 class="text-2xl font-bold">{{ $title }}</h3>
<button @click="modalOpen=false"
{{-- <button @click="modalOpen=false"
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 text-white rounded-full hover:bg-coolgray-300">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</button> --}}
</div>
<div class="relative w-auto pb-8">
{{ $slot }}
@ -48,14 +55,13 @@ class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5
<div class="flex-1"></div>
@if ($isErrorButton)
<x-forms.button @click="modalOpen=false" class="w-24" isError type="button"
wire:click.prevent='{{ $action }}'>Continue
wire:click.prevent="{{ $action }}">Continue
</x-forms.button>
@else
<x-forms.button @click="modalOpen=false" class="w-24" isHighlighted type="button"
wire:click.prevent='{{ $action }}'>Continue
wire:click.prevent="{{ $action }}">Continue
</x-forms.button>
@endif
</div>
</div>
</div>

View File

@ -12,7 +12,7 @@
<x-navbar-subscription />
@endif
<main class="main max-w-screen-2xl">
<main class="mx-auto main max-w-screen-2xl">
{{ $slot }}
</main>
@endsection

View File

@ -1,19 +1,17 @@
<form wire:submit='clone'>
<form>
<div class="flex flex-col">
<h1>Clone</h1>
<div class="subtitle ">Quickly clone all resources to a new project</div>
</div>
<div class="flex items-end gap-2">
<x-forms.input required id="newProjectName" label="New Project Name" />
<x-forms.button isHighlighted type="submit">Clone</x-forms.button>
<div class="subtitle ">Quickly clone all resources to a new project or environment</div>
</div>
<x-forms.input required id="newName" label="New Name" />
<x-forms.button isHighlighted wire:click="clone('project')" class="mt-4">Clone to a new Project</x-forms.button>
<x-forms.button isHighlighted wire:click="clone('environment')" class="mt-4">Clone to a new Environment</x-forms.button>
<h3 class="pt-4 pb-2">Servers</h3>
<div>Choose the server and network to clone the resources to.</div>
<div class="flex flex-col gap-4">
@foreach ($servers->sortBy('id') as $server)
<div class="p-4">
<h4>{{ $server->name }}</h4>
<h5>{{ $server->description }}</h5>
<div class="pt-4 pb-2">Docker Networks</div>
<div class="grid grid-cols-1 gap-2 pb-4 lg:grid-cols-4">
@foreach ($server->destinations() as $destination)
@ -30,9 +28,9 @@
<h3 class="pt-4 pb-2">Resources</h3>
<div>These will be cloned to the new project</div>
<div class="grid grid-cols-1 gap-2 p-4 ">
<div class="grid grid-cols-1 gap-2 pt-4 opacity-95 lg:grid-cols-2 xl:grid-cols-3">
@foreach ($environment->applications->sortBy('name') as $application)
<div>
<div class="cursor-default box-without-bg bg-coolgray-100 group">
<div class="flex flex-col">
<div class="font-bold text-white">{{ $application->name }}</div>
<div class="description">{{ $application->description }}</div>
@ -40,7 +38,7 @@
</div>
@endforeach
@foreach ($environment->databases()->sortBy('name') as $database)
<div>
<div class="cursor-default box-without-bg bg-coolgray-100 group">
<div class="flex flex-col">
<div class="font-bold text-white">{{ $database->name }}</div>
<div class="description">{{ $database->description }}</div>
@ -48,7 +46,7 @@
</div>
@endforeach
@foreach ($environment->services->sortBy('name') as $service)
<div>
<div class="cursor-default box-without-bg bg-coolgray-100 group">
<div class="flex flex-col">
<div class="font-bold text-white">{{ $service->name }}</div>
<div class="description">{{ $service->description }}</div>

View File

@ -8,7 +8,7 @@
<li class="step">Select a Repository, Branch & Save</li>
</ul>
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row">
@foreach ($private_keys as $key)
@forelse ($private_keys as $key)
@if ($private_key_id == $key->id)
<div class="gap-2 py-4 cursor-pointer group hover:bg-coollabs bg-coolgray-200"
wire:click.defer="setPrivateKey('{{ $key->id }}')" wire:key="{{ $key->id }}">
@ -32,7 +32,16 @@ class="loading loading-xs text-warning loading-spinner"></span>
</div>
</div>
@endif
@endforeach
@empty
<div class="flex flex-col items-center justify-center gap-2">
<div class="text-neutral-500">
No private keys found.
</div>
<a href="{{ route('security.private-key.index') }}">
<x-forms.button>Create a new private key</x-forms.button>
</a>
</div>
@endforelse
</div>
@endif
@if ($current_step === 'repository')

View File

@ -46,7 +46,7 @@ class="items-center justify-center box">+ Add New Resource</a>
@else
<div x-data="searchComponent()">
<x-forms.input placeholder="Search for name, fqdn..." class="w-full" x-model="search" />
<div class="grid gap-4 pt-4 lg:grid-cols-4">
<div class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
<template x-for="item in filteredApplications" :key="item.id">
<span class="relative">
<a class="h-24 box group" :href="item.hrefLink">

View File

@ -11,10 +11,15 @@
<div>
<div class="grid grid-cols-1 gap-2 pb-4 lg:grid-cols-4">
@foreach ($server->destinations() as $destination)
<div class="flex flex-col gap-2 box" wire:click="cloneTo('{{ data_get($destination, 'id') }}')">
<div class="font-bold text-white">{{ $server->name }}</div>
<div>{{ $destination->name }}</div>
</div>
<x-new-modal action="cloneTo({{ data_get($destination, 'id') }})">
<x:slot name="content">
<div class="flex flex-col gap-2 box">
<div class="font-bold text-white">{{ $server->name }}</div>
<div>{{ $destination->name }}</div>
</div>
</x:slot>
<div>You are about to clone this resource.</div>
</x-new-modal>
@endforeach
</div>
</div>
@ -32,10 +37,15 @@ class="font-bold text-warning">{{ $resource->environment->project->name }} /
@forelse ($projects as $project)
<div class="flex flex-row flex-wrap gap-2">
@foreach ($project->environments as $environment)
<div class="flex flex-col gap-2 box" wire:click="moveTo('{{ data_get($environment, 'id') }}')">
<div class="font-bold text-white">{{ $project->name }}</div>
<div><span class="text-warning">{{ $environment->name }}</span> environment</div>
</div>
<x-new-modal action="moveTo({{ data_get($environment, 'id') }})">
<x:slot name="content">
<div class="flex flex-col gap-2 box">
<div class="font-bold text-white">{{ $project->name }}</div>
<div><span class="text-warning">{{ $environment->name }}</span> environment</div>
</div>
</x:slot>
<div>You are about to move this resource.</div>
</x-new-modal>
@endforeach
</div>
@empty

View File

@ -4,7 +4,7 @@
@forelse ($this->resource->tags as $tagId => $tag)
<div class="px-2 py-1 text-center text-white select-none w-fit bg-coolgray-100 hover:bg-coolgray-200">
{{ $tag->name }}
<svg wire:click="deleteTag('{{ $tag->id }}','{{ $tag->name }}')"
<svg wire:click="deleteTag('{{ $tag->id }}')"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
class="inline-block w-3 h-3 rounded cursor-pointer stroke-current hover:bg-red-500">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
@ -17,16 +17,19 @@ class="inline-block w-3 h-3 rounded cursor-pointer stroke-current hover:bg-red-5
<form wire:submit='submit' class="flex items-end gap-2 pt-4">
<div class="w-64">
<x-forms.input label="Create new or assign existing tags"
helper="You add more at once with space seperated list: web api something<br><br>If the tag does not exists, it will be created." wire:model="new_tag" />
helper="You add more at once with space seperated list: web api something<br><br>If the tag does not exists, it will be created."
wire:model="new_tag" />
</div>
<x-forms.button type="submit">Add</x-forms.button>
</form>
<h3 class="pt-4">Already defined tags</h3>
<div>Click to quickly add</div>
<div class="flex gap-2 pt-4">
@foreach ($tags as $tag)
<x-forms.button wire:click="addTag('{{ $tag->id }}','{{ $tag->name }}')">
{{ $tag->name }}</x-forms.button>
@endforeach
</div>
@if ($tags->count() > 0)
<h3 class="pt-4">Already defined tags</h3>
<div>Click to quickly add one.</div>
<div class="flex gap-2 pt-4">
@foreach ($tags as $tag)
<x-forms.button wire:click="addTag('{{ $tag->id }}','{{ $tag->name }}')">
{{ $tag->name }}</x-forms.button>
@endforeach
</div>
@endif
</div>

View File

@ -1,11 +1,14 @@
<div>
<h1>Tags</h1>
<div>Here you can see all the tags here</div>
<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
<div>No tags yet defined yet. Go to a resource and add a tag there.</div>
@endforelse
<div class="flex flex-col gap-2 pb-6 ">
<div>Available tags: </div>
<div class="flex flex-wrap gap-2">
@forelse ($tags as $oneTag)
<a class="flex items-center justify-center h-6 px-2 text-white min-w-14 w-fit hover:no-underline hover:bg-coolgray-200 bg-coolgray-100"
href="{{ route('tags.show', ['tag_name' => $oneTag->name]) }}">{{ $oneTag->name }}</a>
@empty
<div>No tags yet defined yet. Go to a resource and add a tag there.</div>
@endforelse
</div>
</div>
</div>

View File

@ -1,24 +1,46 @@
<div>
<div class="flex items-start gap-2">
<div>
<h1>Tag: {{ $tag->name }}</h1>
<div class="pt-2">Tag details</div>
<h1>Tags</h1>
</div>
</div>
<div class="pt-4">
<div class="flex flex-col gap-2 pb-6 ">
<div>Available tags: </div>
<div class="flex flex-wrap gap-2 ">
@forelse ($tags as $oneTag)
<a :class="{{ $tag->id == $oneTag->id }} && 'bg-coollabs hover:bg-coollabs-100'"
class="flex items-center justify-center h-6 px-2 text-white min-w-14 w-fit hover:no-underline hover:bg-coolgray-200 bg-coolgray-100"
href="{{ route('tags.show', ['tag_name' => $oneTag->name]) }}">{{ $oneTag->name }}</a>
@empty
<div>No tags yet defined yet. Go to a resource and add a tag there.</div>
@endforelse
</div>
</div>
<div>
<h3 class="py-4">Details</h3>
<div class="flex items-end gap-2 ">
<div class="w-[500px]">
<x-forms.input readonly label="Deploy Webhook URL" id="webhook" />
</div>
<x-new-modal buttonTitle="Redeploy All" action="redeploy_all" class="mt-1">
<x-new-modal isHighlighted buttonTitle="Redeploy All" action="redeploy_all">
All resources will be redeployed.
</x-new-modal>
</div>
<div class="grid gap-2 pt-4 lg:grid-cols-4">
@foreach ($resources as $resource)
<a href="{{ $resource->link() }}" class="flex flex-col box group">
<span class="font-bold text-white">{{ $resource->name }}</span>
<span class="description">{{ $resource->description }}</span>
<div class="grid grid-cols-1 gap-2 pt-4 lg:grid-cols-2 xl:grid-cols-3">
@foreach ($applications as $application)
<a href="{{ $application->link() }}" class="flex flex-col box group">
<span
class="font-bold text-white">{{ $application->project()->name }}/{{ $application->environment->name }}</span>
<span class="text-white ">{{ $application->name }}</span>
<span class="description">{{ $application->description }}</span>
</a>
@endforeach
@foreach ($services as $service)
<a href="{{ $service->link() }}" class="flex flex-col box group">
<span
class="font-bold text-white">{{ $service->project()->name }}/{{ $service->environment->name }}</span>
<span class="text-white ">{{ $service->name }}</span>
<span class="description">{{ $service->description }}</span>
</a>
@endforeach
</div>

View File

@ -824,8 +824,12 @@
if (!$team) {
throw new Exception('No team found for subscription: ' . $subscription->id);
}
SubscriptionInvoiceFailedJob::dispatch($team);
send_internal_notification('Invoice payment failed: ' . $subscription->team->id);
if (!$subscription->stripe_invoice_paid) {
SubscriptionInvoiceFailedJob::dispatch($team);
send_internal_notification('Invoice payment failed: ' . $subscription->team->id);
} else {
send_internal_notification('Invoice payment failed but already paid: ' . $subscription->team->id);
}
break;
case 'payment_intent.payment_failed':
$customerId = data_get($data, 'customer');

View File

@ -4,7 +4,7 @@
"version": "3.12.36"
},
"v4": {
"version": "4.0.0-beta.205"
"version": "4.0.0-beta.206"
}
}
}