Merge pull request #1793 from coollabsio/next

v4.0.0-beta.229
This commit is contained in:
Andras Bacsai 2024-03-01 11:47:14 +01:00 committed by GitHub
commit bb927505fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 141 additions and 153 deletions

View File

@ -218,12 +218,12 @@ public function handle(): void
$teamId = data_get($this->application, 'environment.project.team.id');
$buildServers = Server::buildServers($teamId)->get();
if ($buildServers->count() === 0) {
$this->application_deployment_queue->addLogEntry("Build server feature activated, but no suitable build server found. Using the deployment server.");
$this->application_deployment_queue->addLogEntry("No suitable build server found. Using the deployment server.");
$this->build_server = $this->server;
$this->original_server = $this->server;
} else {
$this->application_deployment_queue->addLogEntry("Build server feature activated and found a suitable build server. Using it to build your application - if needed.");
$this->build_server = $buildServers->random();
$this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name}).");
$this->original_server = $this->server;
$this->use_build_server = true;
}
@ -427,13 +427,13 @@ private function deploy_docker_compose_buildpack()
}
private function deploy_dockerfile_buildpack()
{
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
if ($this->use_build_server) {
$this->server = $this->build_server;
}
if (data_get($this->application, 'dockerfile_location')) {
$this->dockerfile_location = $this->application->dockerfile_location;
}
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
@ -528,9 +528,11 @@ private function write_deployment_configurations()
$this->server = $this->original_server;
}
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
$composeFileName = "$this->configuration_dir/docker-compose.yml";
if ($this->pull_request_id !== 0) {
if ($this->pull_request_id === 0) {
$composeFileName = "$this->configuration_dir/docker-compose.yml";
} else {
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yml";
}
$this->execute_remote_command(
[
@ -725,7 +727,7 @@ private function rolling_update()
$this->write_deployment_configurations();
$this->server = $this->original_server;
}
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled) {
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || $this->application->pull_request_id !== 0) {
$this->application_deployment_queue->addLogEntry("----------------------------------------");
if (count($this->application->ports_mappings_array) > 0) {
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
@ -733,6 +735,10 @@ private function rolling_update()
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
}
if ($this->application->pull_request_id !== 0) {
$this->application->settings->is_consistent_container_name_enabled = true;
$this->application_deployment_queue->addLogEntry("Pull request deployment, rolling update is not supported.");
}
$this->stop_running_container(force: true);
$this->start_by_compose_file();
} else {
@ -810,26 +816,9 @@ private function deploy_pull_request()
$this->add_build_env_variables_to_dockerfile();
}
$this->build_image();
$this->stop_running_container();
if ($this->application->destination->server->isSwarm()) {
$this->push_to_docker_registry();
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker stack deploy --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}-{$this->pull_request_id}")
],
);
} else {
$this->application_deployment_queue->addLogEntry("Starting preview deployment.");
if ($this->use_build_server) {
$this->execute_remote_command(
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
);
}
}
$this->push_to_docker_registry();
// $this->stop_running_container();
$this->rolling_update();
}
private function create_workdir()
{
@ -1226,43 +1215,45 @@ private function generate_compose_file()
// ];
// }
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if (count($custom_compose) > 0) {
$ipv4 = data_get($custom_compose, 'ip.0');
$ipv6 = data_get($custom_compose, 'ip6.0');
data_forget($custom_compose, 'ip');
data_forget($custom_compose, 'ip6');
if ($ipv4 || $ipv6) {
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
if ($this->application->pull_request_id === 0) {
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if (count($custom_compose) > 0) {
$ipv4 = data_get($custom_compose, 'ip.0');
$ipv6 = data_get($custom_compose, 'ip6.0');
data_forget($custom_compose, 'ip');
data_forget($custom_compose, 'ip6');
if ($ipv4 || $ipv6) {
data_forget($docker_compose['services'][$this->container_name], 'networks');
}
if ($ipv4) {
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
}
if ($ipv6) {
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
}
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
}
if ($ipv4) {
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
} else {
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
data_forget($docker_compose, 'services.' . $this->container_name);
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if (count($custom_compose) > 0) {
$ipv4 = data_get($custom_compose, 'ip.0');
$ipv6 = data_get($custom_compose, 'ip6.0');
data_forget($custom_compose, 'ip');
data_forget($custom_compose, 'ip6');
if ($ipv4 || $ipv6) {
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
}
if ($ipv4) {
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
}
if ($ipv6) {
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
}
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
}
if ($ipv6) {
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
}
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
}
} else {
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
data_forget($docker_compose, 'services.' . $this->container_name);
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if (count($custom_compose) > 0) {
$ipv4 = data_get($custom_compose, 'ip.0');
$ipv6 = data_get($custom_compose, 'ip6.0');
data_forget($custom_compose, 'ip');
data_forget($custom_compose, 'ip6');
if ($ipv4 || $ipv6) {
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
}
if ($ipv4) {
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
}
if ($ipv6) {
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
}
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
}
}
@ -1539,18 +1530,18 @@ private function stop_running_container(bool $force = false)
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
if ($this->pull_request_id === 0) {
$containers = $containers->filter(function ($container) {
return data_get($container, 'Names') !== $this->container_name;
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name . '-pr-' . $this->pull_request_id;
});
}
$containers->each(function ($container) {
$containerName = data_get($container, 'Names');
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
["docker rm -f $containerName >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
);
});
if ($this->application->settings->is_consistent_container_name_enabled) {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
);
}
} else {
@ -1559,7 +1550,7 @@ private function stop_running_container(bool $force = false)
'status' => ApplicationDeploymentStatus::FAILED->value,
]);
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
);
}
}
@ -1680,7 +1671,7 @@ public function failed(Throwable $exception): void
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
$this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr');
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true]
);
}
}

View File

@ -30,6 +30,9 @@ public function __construct(
public function handle()
{
try {
if ($this->application->is_public_repository()) {
return;
}
if ($this->status === ProcessStatus::CLOSED) {
$this->delete_comment();
return;

View File

@ -10,7 +10,7 @@ class Configuration extends Component
{
public Application $application;
public $servers;
protected $listeners = ['build_pack_updated' => '$refresh'];
protected $listeners = ['buildPackUpdated' => '$refresh'];
public function mount()
{

View File

@ -182,7 +182,7 @@ public function updatedApplicationBuildPack()
$this->resetDefaultLabels(false);
}
$this->submit();
$this->dispatch('build_pack_updated');
$this->dispatch('buildPackUpdated');
}
public function getWildcardDomain()
{

View File

@ -17,9 +17,7 @@ public function getListeners()
{
$userId = auth()->user()->id;
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'checkStatus',
"refreshStacks",
"checkStatus",
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
];
}
public function render()
@ -37,21 +35,10 @@ public function mount()
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
}
public function checkStatus()
public function check_status()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStacks();
$this->dispatch('refresh')->self();
$this->dispatch('serviceStatusChanged');
}
public function refreshStacks()
{
$this->applications = $this->service->applications->sort();
$this->applications->each(function ($application) {
$application->refresh();
});
$this->databases = $this->service->databases->sort();
$this->databases->each(function ($database) {
$database->refresh();
});
}
}

View File

@ -17,7 +17,20 @@ class Navbar extends Component
public array $parameters;
public array $query;
public $isDeploymentProgress = false;
public function getListeners()
{
return [
"serviceStatusChanged"
];
}
public function serviceStatusChanged()
{
$this->dispatch('refresh')->self();
}
public function render()
{
return view('livewire.project.service.navbar');
}
public function checkDeployments()
{
$activity = Activity::where('properties->type_uuid', $this->service->uuid)->latest()->first();
@ -28,26 +41,6 @@ public function checkDeployments()
$this->isDeploymentProgress = false;
}
}
public function getListeners()
{
return [
"serviceStatusChanged"
];
}
public function serviceStatusChanged()
{
$this->service->refresh();
}
public function render()
{
return view('livewire.project.service.navbar');
}
public function check_status($showNotification = false)
{
dispatch_sync(new ContainerStatusJob($this->service->destination->server));
$this->service->refresh();
if ($showNotification) $this->dispatch('success', 'Service status updated.');
}
public function deploy()
{
$this->checkDeployments();
@ -62,7 +55,6 @@ public function deploy()
public function stop(bool $forceCleanup = false)
{
StopService::run($this->service);
$this->service->refresh();
if ($forceCleanup) {
$this->dispatch('success', 'Containers cleaned up.');
} else {

View File

@ -65,6 +65,12 @@ public function additional_networks()
return $this->belongsToMany(StandaloneDocker::class, 'additional_destinations')
->withPivot('server_id', 'status');
}
public function is_public_repository(): bool {
if (data_get($this, 'source.is_public')) {
return true;
}
return false;
}
public function is_github_based(): bool
{
if (data_get($this, 'source')) {

View File

@ -62,6 +62,9 @@ public function serverOverflow() {
}
static public function serverLimit()
{
if (currentTeam()->id === 0 && isDev()) {
return 9999999;
}
return Team::find(currentTeam()->id)->limits['serverLimit'];
}
public function limits(): Attribute

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.228',
'release' => '4.0.0-beta.229',
// 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.228';
return '4.0.0-beta.229';

View File

@ -4,8 +4,8 @@
<h2>Advanced</h2>
</div>
<div>Advanced configuration for your application.</div>
<div class="flex flex-col pt-4 ">
<h4>General</h4>
<div class="flex flex-col pt-4 w-96">
<h3>General</h3>
@if ($application->git_based())
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
id="application.settings.is_auto_deploy_enabled" label="Auto Deploy" />
@ -20,50 +20,52 @@
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold text-warning'>You will lose the rolling update feature!</span>"
instantSave id="application.settings.is_consistent_container_name_enabled"
label="Consistent Container Names" />
<h4>Logs</h4>
<h3>Logs</h3>
@if (!$application->settings->is_raw_compose_deployment_enabled)
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave id="application.settings.is_log_drain_enabled" label="Drain Logs" />
@endif
@if ($application->git_based())
<h4>Git</h4>
<x-forms.checkbox instantSave id="application.settings.is_git_submodules_enabled" label="Git Submodules"
<h3>Git</h3>
<x-forms.checkbox instantSave id="application.settings.is_git_submodules_enabled" label="Submodules"
helper="Allow Git Submodules during build process." />
<x-forms.checkbox instantSave id="application.settings.is_git_lfs_enabled" label="Git LFS"
<x-forms.checkbox instantSave id="application.settings.is_git_lfs_enabled" label="LFS"
helper="Allow Git LFS during build process." />
@endif
<h4>GPU</h4>
<form wire:submit="submit">
@if ($application->build_pack !== 'dockercompose')
<div class="flex gap-2">
<x-forms.checkbox
helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='text-white underline' target='_blank'>here</a>."
instantSave id="application.settings.is_gpu_enabled" label="Attach GPU" />
@if ($application->settings->is_gpu_enabled)
<x-forms.button type="submiot">Save</x-forms.button>
@endif
</div>
@endif
@if ($application->settings->is_gpu_enabled)
<div class="flex flex-col w-full gap-2 p-2 xl:flex-row">
<x-forms.input label="GPU Driver" id="application.settings.gpu_driver"> </x-forms.input>
<x-forms.input label="GPU Count" placeholder="empty means use all GPUs"
id="application.settings.gpu_count"> </x-forms.input>
<x-forms.input label="GPU Device Ids" placeholder="0,2"
helper="Comma separated list of device ids. More info <a href='https://docs.docker.com/compose/gpu-support/#access-specific-devices' class='text-white underline' target='_blank'>here</a>."
id="application.settings.gpu_device_ids"> </x-forms.input>
</div>
<div class="px-2">
<x-forms.textarea label="GPU Options" id="application.settings.gpu_options">
</x-forms.textarea>
</div>
@endif
</form>
{{-- <x-forms.checkbox disabled instantSave id="is_dual_cert" label="Dual Certs?" />
<x-forms.checkbox disabled instantSave id="is_custom_ssl" label="Is Custom SSL?" />
<x-forms.checkbox disabled instantSave id="is_http2" label="Is Http2?" /> --}}
</div>
<h3>GPU</h3>
<form wire:submit="submit">
@if ($application->build_pack !== 'dockercompose')
<div class="w-96">
<x-forms.checkbox
helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='text-white underline' target='_blank'>here</a>."
instantSave id="application.settings.is_gpu_enabled" label="Attach GPU" />
@if ($application->settings->is_gpu_enabled)
<h5>GPU Settings</h5>
<x-forms.button type="submit">Save</x-forms.button>
@endif
</div>
@endif
@if ($application->settings->is_gpu_enabled)
<div class="flex flex-col w-full gap-2 p-2 xl:flex-row">
<x-forms.input label="GPU Driver" id="application.settings.gpu_driver"> </x-forms.input>
<x-forms.input label="GPU Count" placeholder="empty means use all GPUs"
id="application.settings.gpu_count"> </x-forms.input>
<x-forms.input label="GPU Device Ids" placeholder="0,2"
helper="Comma separated list of device ids. More info <a href='https://docs.docker.com/compose/gpu-support/#access-specific-devices' class='text-white underline' target='_blank'>here</a>."
id="application.settings.gpu_device_ids"> </x-forms.input>
</div>
<div class="px-2">
<x-forms.textarea label="GPU Options" id="application.settings.gpu_options">
</x-forms.textarea>
</div>
@endif
</form>
</div>
</div>

View File

@ -8,7 +8,8 @@
@if ($isConfigurationChanged && !is_null($application->config_hash) && !$application->isExited())
<div title="Configuration not applied to the running application. You need to redeploy.">
<svg class="w-6 h-6 text-warning" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16"/>
<path fill="currentColor"
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
</svg>
</div>
@endif
@ -100,11 +101,15 @@ class="underline" href="https://coolify.io/docs/docker/registry"
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
@endif
@else
@if ($application->destination->server->isSwarm() || $application->additional_servers->count() > 0)
<x-forms.input id="application.docker_registry_image_name" required label="Docker Image" />
@if (
$application->destination->server->isSwarm() ||
$application->additional_servers->count() > 0 ||
$application->settings->is_build_server_enabled)
<x-forms.input id="application.docker_registry_image_name" required label="Docker Image"
placeholder="Required!" />
<x-forms.input id="application.docker_registry_image_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" />
placeholder="Empty means latest will be used." label="Docker Image Tag" />
@else
<x-forms.input id="application.docker_registry_image_name"
helper="Empty means it won't push the image to a docker registry."

View File

@ -1,4 +1,4 @@
<nav wire:poll.30000ms="check_status">
<nav wire:poll.5000ms="check_status">
<x-resources.breadcrumbs :resource="$database" :parameters="$parameters" />
<x-databases.navbar :database="$database" :parameters="$parameters" />
</nav>

View File

@ -45,7 +45,7 @@ class="font-normal text-white normal-case border-none rounded hover:no-underline
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" />
<x-forms.input autofocus="true" placeholder="Search for name, fqdn..." class="w-full" x-model="search" />
<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>

View File

@ -1,4 +1,4 @@
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.checkStatus">
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.check_status" wire:poll.5000ms="check_status">
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
<div class="flex h-full pt-6">
<div class="flex flex-col items-start gap-4 min-w-fit">

View File

@ -11,7 +11,7 @@ class="inline-block w-3 h-3 rounded cursor-pointer stroke-current hover:bg-red-5
</svg>
</div>
@empty
<div>No tags yet</div>
<div class="py-1">No tags yet</div>
@endforelse
</div>
<form wire:submit='submit' class="flex items-end gap-2 pt-4">
@ -23,8 +23,7 @@ class="inline-block w-3 h-3 rounded cursor-pointer stroke-current hover:bg-red-5
<x-forms.button type="submit">Add</x-forms.button>
</form>
@if ($tags->count() > 0)
<h3 class="pt-4">Already defined tags</h3>
<div>Click to quickly add one.</div>
<h3 class="pt-4">Quickly Add</h3>
<div class="flex gap-2 pt-4">
@foreach ($tags as $tag)
<x-forms.button wire:click="addTag('{{ $tag->id }}','{{ $tag->name }}')">

View File

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