diff --git a/app/Console/Commands/Horizon.php b/app/Console/Commands/Horizon.php
new file mode 100644
index 000000000..8dd64a246
--- /dev/null
+++ b/app/Console/Commands/Horizon.php
@@ -0,0 +1,21 @@
+info('Horizon is enabled. Starting.');
+ $this->call('horizon');
+ exit(0);
+ } else {
+ exit(0);
+ }
+ }
+}
diff --git a/app/Console/Commands/Scheduler.php b/app/Console/Commands/Scheduler.php
new file mode 100644
index 000000000..eab623802
--- /dev/null
+++ b/app/Console/Commands/Scheduler.php
@@ -0,0 +1,21 @@
+info('Scheduler is enabled. Starting.');
+ $this->call('schedule:work');
+ exit(0);
+ } else {
+ exit(0);
+ }
+ }
+}
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index 1346a6ded..98c189275 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -298,6 +298,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"ignore_errors" => true,
]
);
+
+
// $this->execute_remote_command(
// [
// "docker image prune -f >/dev/null 2>&1",
@@ -305,6 +307,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// "ignore_errors" => true,
// ]
// );
+
+
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
}
}
@@ -417,7 +421,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
]);
}
- $this->write_deployment_configurations();
// Start compose file
if ($this->application->settings->is_raw_compose_deployment_enabled) {
@@ -425,7 +428,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
);
+ $this->write_deployment_configurations();
} else {
+ $this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
ray("SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
$this->execute_remote_command(
@@ -437,14 +442,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
);
+ $this->write_deployment_configurations();
} 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 -d"), "hidden" => true],
);
+ $this->write_deployment_configurations();
}
}
-
$this->application_deployment_queue->addLogEntry("New container started.");
}
private function deploy_dockerfile_buildpack()
@@ -822,6 +828,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function deploy_pull_request()
{
+ if ($this->application->build_pack === 'dockercompose') {
+ $this->deploy_docker_compose_buildpack();
+ return;
+ }
if ($this->use_build_server) {
$this->server = $this->build_server;
}
diff --git a/app/Livewire/Notifications/EmailSettings.php b/app/Livewire/Notifications/EmailSettings.php
index b6152907d..a45381b84 100644
--- a/app/Livewire/Notifications/EmailSettings.php
+++ b/app/Livewire/Notifications/EmailSettings.php
@@ -119,16 +119,18 @@ class EmailSettings extends Component
{
try {
$this->resetErrorBag();
- $this->validate([
- 'team.smtp_from_address' => 'required|email',
- 'team.smtp_from_name' => 'required',
- 'team.smtp_host' => 'required',
- 'team.smtp_port' => 'required|numeric',
- 'team.smtp_encryption' => 'nullable',
- 'team.smtp_username' => 'nullable',
- 'team.smtp_password' => 'nullable',
- 'team.smtp_timeout' => 'nullable',
- ]);
+ if (!$this->team->use_instance_email_settings) {
+ $this->validate([
+ 'team.smtp_from_address' => 'required|email',
+ 'team.smtp_from_name' => 'required',
+ 'team.smtp_host' => 'required',
+ 'team.smtp_port' => 'required|numeric',
+ 'team.smtp_encryption' => 'nullable',
+ 'team.smtp_username' => 'nullable',
+ 'team.smtp_password' => 'nullable',
+ 'team.smtp_timeout' => 'nullable',
+ ]);
+ }
$this->team->save();
refreshSession();
$this->dispatch('success', 'Settings saved.');
diff --git a/app/Livewire/Project/Application/Deployment/Index.php b/app/Livewire/Project/Application/Deployment/Index.php
index 520848a54..d8e033b24 100644
--- a/app/Livewire/Project/Application/Deployment/Index.php
+++ b/app/Livewire/Project/Application/Deployment/Index.php
@@ -9,7 +9,7 @@ use Livewire\Component;
class Index extends Component
{
public Application $application;
- public array|Collection $deployments = [];
+ public ?Collection $deployments;
public int $deployments_count = 0;
public string $current_url;
public int $skip = 0;
@@ -48,9 +48,9 @@ class Index extends Component
}
private function show_more()
{
- if (count($this->deployments) !== 0) {
+ if ($this->deployments->count() !== 0) {
$this->show_next = true;
- if (count($this->deployments) < $this->default_take) {
+ if ($this->deployments->count() < $this->default_take) {
$this->show_next = false;
}
return;
@@ -63,7 +63,6 @@ class Index extends Component
}
public function previous_page(?int $take = null)
{
-
if ($take) {
$this->skip = $this->skip - $take;
}
diff --git a/app/Livewire/Project/Shared/ScheduledTask/Add.php b/app/Livewire/Project/Shared/ScheduledTask/Add.php
index 3cc5428b8..3a7a3fa23 100644
--- a/app/Livewire/Project/Shared/ScheduledTask/Add.php
+++ b/app/Livewire/Project/Shared/ScheduledTask/Add.php
@@ -33,19 +33,23 @@ class Add extends Component
public function submit()
{
- $this->validate();
- $isValid = validate_cron_expression($this->frequency);
- if (!$isValid) {
- $this->dispatch('error', 'Invalid Cron / Human expression.');
- return;
+ try {
+ $this->validate();
+ $isValid = validate_cron_expression($this->frequency);
+ if (!$isValid) {
+ $this->dispatch('error', 'Invalid Cron / Human expression.');
+ return;
+ }
+ $this->dispatch('saveScheduledTask', [
+ 'name' => $this->name,
+ 'command' => $this->command,
+ 'frequency' => $this->frequency,
+ 'container' => $this->container,
+ ]);
+ $this->clear();
+ } catch (\Exception $e) {
+ return handleError($e, $this);
}
- $this->dispatch('saveScheduledTask', [
- 'name' => $this->name,
- 'command' => $this->command,
- 'frequency' => $this->frequency,
- 'container' => $this->container,
- ]);
- $this->clear();
}
public function clear()
diff --git a/app/Livewire/Tags/Index.php b/app/Livewire/Tags/Index.php
index eba25a750..75ed06f7b 100644
--- a/app/Livewire/Tags/Index.php
+++ b/app/Livewire/Tags/Index.php
@@ -2,14 +2,73 @@
namespace App\Livewire\Tags;
+use App\Http\Controllers\Api\Deploy;
+use App\Models\ApplicationDeploymentQueue;
use App\Models\Tag;
+use Illuminate\Support\Collection;
+use Livewire\Attributes\Url;
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');
+ #[Url()]
+ public ?string $tag = null;
+
+ public Collection $tags;
+ public Collection $applications;
+ public Collection $services;
+ public $webhook = null;
+ public $deployments_per_tag_per_server = [];
+
+ public function updatedTag()
+ {
+ $tag = $this->tags->where('name', $this->tag)->first();
+ $this->webhook = generatTagDeployWebhook($tag->name);
+ $this->applications = $tag->applications()->get();
+ $this->services = $tag->services()->get();
+ $this->get_deployments();
+ }
+ public function get_deployments()
+ {
+ try {
+ $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",
+ "application_name",
+ "deployment_url",
+ "pull_request_id",
+ "server_name",
+ "server_id",
+ "status"
+ ])->sortBy('id')->groupBy('server_name')->toArray();
+ } catch (\Exception $e) {
+ return handleError($e, $this);
+ }
+ }
+ public function redeploy_all()
+ {
+ try {
+ $message = collect([]);
+ $this->applications->each(function ($resource) use ($message) {
+ $deploy = new Deploy();
+ $message->push($deploy->deploy_resource($resource));
+ });
+ $this->services->each(function ($resource) use ($message) {
+ $deploy = new Deploy();
+ $message->push($deploy->deploy_resource($resource));
+ });
+ $this->dispatch('success', 'Mass deployment started.');
+ } catch (\Exception $e) {
+ return handleError($e, $this);
+ }
+ }
+ public function mount()
+ {
+ $this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
+ if ($this->tag) {
+ $this->updatedTag();
+ }
}
public function render()
{
diff --git a/app/View/Components/Forms/Button.php b/app/View/Components/Forms/Button.php
index c9f4ebdc0..350039fce 100644
--- a/app/View/Components/Forms/Button.php
+++ b/app/View/Components/Forms/Button.php
@@ -16,7 +16,7 @@ class Button extends Component
public bool $isModal = false,
public bool $noStyle = false,
public ?string $modalId = null,
- public string $defaultClass = "btn btn-primary btn-sm font-normal text-white normal-case no-animation rounded border-none"
+ public string $defaultClass = "button"
) {
if ($this->noStyle) {
$this->defaultClass = "";
diff --git a/app/View/Components/Forms/Checkbox.php b/app/View/Components/Forms/Checkbox.php
index 0bbbc1e04..36b561eda 100644
--- a/app/View/Components/Forms/Checkbox.php
+++ b/app/View/Components/Forms/Checkbox.php
@@ -12,14 +12,14 @@ class Checkbox extends Component
* Create a new component instance.
*/
public function __construct(
- public string|null $id = null,
- public string|null $name = null,
- public string|null $value = null,
- public string|null $label = null,
- public string|null $helper = null,
- public string|bool $instantSave = false,
+ public ?string $id = null,
+ public ?string $name = null,
+ public ?string $value = null,
+ public ?string $label = null,
+ public ?string $helper = null,
+ public string|bool $instantSave = false,
public bool $disabled = false,
- public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700",
+ public string $defaultClass = "border-coolgray-500 text-warning focus:ring-warning dark:bg-coolgray-100 rounded cursor-pointer dark:disabled:bg-base dark:disabled:cursor-not-allowed",
) {
//
}
diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php
index a55f9b9d2..56349f6db 100644
--- a/app/View/Components/Forms/Input.php
+++ b/app/View/Components/Forms/Input.php
@@ -21,7 +21,7 @@ class Input extends Component
public ?string $helper = null,
public bool $allowToPeak = true,
public bool $isMultiline = false,
- public string $defaultClass = "input input-sm bg-coolgray-100 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
+ public string $defaultClass = "input",
) {
}
diff --git a/app/View/Components/Forms/Select.php b/app/View/Components/Forms/Select.php
index e1f2fc759..99bcf1873 100644
--- a/app/View/Components/Forms/Select.php
+++ b/app/View/Components/Forms/Select.php
@@ -14,12 +14,12 @@ class Select extends Component
* Create a new component instance.
*/
public function __construct(
- public string|null $id = null,
- public string|null $name = null,
- public string|null $label = null,
- public string|null $helper = null,
- public bool $required = false,
- public string $defaultClass = "select select-sm w-full rounded text-sm bg-coolgray-100 font-normal disabled:bg-coolgray-200/50 disabled:border-none"
+ public ?string $id = null,
+ public ?string $name = null,
+ public ?string $label = null,
+ public ?string $helper = null,
+ public bool $required = false,
+ public string $defaultClass = "block w-full py-1.5 rounded border-0 text-sm ring-inset ring-1 dark:ring-coolgray-300 dark:placeholder:text-neutral-700 focus:ring-2 focus:ring-inset dark:focus:ring-coolgray-500 dark:bg-coolgray-100 dark:text-white text-black "
) {
//
}
diff --git a/app/View/Components/Forms/Textarea.php b/app/View/Components/Forms/Textarea.php
index 8c50af533..c9d2c26ba 100644
--- a/app/View/Components/Forms/Textarea.php
+++ b/app/View/Components/Forms/Textarea.php
@@ -25,8 +25,8 @@ class Textarea extends Component
public ?string $helper = null,
public bool $realtimeValidation = false,
public bool $allowToPeak = true,
- public string $defaultClass = "textarea leading-normal bg-coolgray-100 rounded text-white w-full scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50",
- public string $defaultClassInput = "input input-sm bg-coolgray-100 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
+ public string $defaultClass = "block w-full py-1.5 rounded border-0 text-sm ring-inset ring-1 dark:bg-coolgray-100 dark:text-white text-black focus:ring-2 dark:focus:ring-coolgray-300 dark:ring-coolgray-300 scrollbar dark:read-only:text-neutral-500",
+ public string $defaultClassInput = "block w-full py-1.5 rounded border-0 text-sm ring-inset ring-1 dark:bg-coolgray-100 dark:text-white text-black focus:ring-2 dark:focus:ring-coolgray-300 dark:ring-coolgray-300 dark:read-only:text-neutral-500"
) {
//
}
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index c9f98e48a..7810e0155 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -1240,84 +1240,95 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
$baseName = generateApplicationContainerName($resource, $pull_request_id);
$containerName = "$serviceName-$baseName";
- if ($pull_request_id !== 0) {
- if (count($serviceVolumes) > 0) {
- $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $pull_request_id, $topLevelVolumes) {
- if (is_string($volume)) {
- $volume = str($volume);
- if ($volume->contains(':') && !$volume->startsWith('/')) {
- $name = $volume->before(':');
- $mount = $volume->after(':');
- $newName = $resource->uuid . "-{$name}-pr-$pull_request_id";
- $volume = str("$newName:$mount");
- $topLevelVolumes->put($newName, [
- 'name' => $newName,
- ]);
- }
- } else if (is_array($volume)) {
- $source = data_get($volume, 'source');
- if ($source) {
- $newSource = $resource->uuid . "-{$source}-pr-$pull_request_id";
- data_set($volume, 'source', $newSource);
- if (!str($source)->startsWith('/')) {
- $topLevelVolumes->put($newSource, [
- 'name' => $newSource,
- ]);
+ if (count($serviceVolumes) > 0) {
+ $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
+ if (is_string($volume)) {
+ $volume = str($volume);
+ if ($volume->contains(':') && !$volume->startsWith('/')) {
+ $name = $volume->before(':');
+ $mount = $volume->after(':');
+ if ($name->startsWith('.') || $name->startsWith('~')) {
+ $dir = base_configuration_dir() . '/applications/' . $resource->uuid;
+ if ($name->startsWith('.')) {
+ $name = $name->replaceFirst('.', $dir);
}
- }
- }
- return $volume->value();
- });
- data_set($service, 'volumes', $serviceVolumes->toArray());
- }
- } else {
- if (count($serviceVolumes) > 0) {
- $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes) {
- if (is_string($volume)) {
- $volume = str($volume);
- if ($volume->contains(':') && !$volume->startsWith('/')) {
- $name = $volume->before(':');
- $mount = $volume->after(':');
- if ($name->startsWith('.') || $name->startsWith('~')) {
- $dir = base_configuration_dir() . '/applications/' . $resource->uuid;
- if ($name->startsWith('.')) {
- $name = $name->replaceFirst('.', $dir);
- }
- if ($name->startsWith('~')) {
- $name = $name->replaceFirst('~', $dir);
- }
+ if ($name->startsWith('~')) {
+ $name = $name->replaceFirst('~', $dir);
+ }
+ if ($pull_request_id !== 0) {
+ $name = $name . "-pr-$pull_request_id";
+ }
+ $volume = str("$name:$mount");
+ } else {
+ if ($pull_request_id !== 0) {
+ $name = $name . "-pr-$pull_request_id";
$volume = str("$name:$mount");
+ $topLevelVolumes->put($name, [
+ 'name' => $name,
+ ]);
} else {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
}
}
- } else if (is_array($volume)) {
- $source = data_get($volume, 'source');
- if ($source) {
- if ((str($source)->startsWith('.') || str($source)->startsWith('~')) && !str($source)->startsWith('/')) {
- $dir = base_configuration_dir() . '/applications/' . $resource->uuid;
- if (str($source, '.')) {
- $source = str('.', $dir, $source);
- }
- if (str($source, '~')) {
- $source = str('~', $dir, $source);
- }
- data_set($volume, 'source', $source);
+ } else {
+ if ($volume->startsWith('/')) {
+ $name = $volume->before(':');
+ $mount = $volume->after(':');
+ if ($pull_request_id !== 0) {
+ $name = $name . "-pr-$pull_request_id";
+ }
+ $volume = str("$name:$mount");
+ }
+ }
+
+ } else if (is_array($volume)) {
+ $source = data_get($volume, 'source');
+ $target = data_get($volume, 'target');
+ $read_only = data_get($volume, 'read_only');
+ if ($source && $target) {
+ if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) {
+ $dir = base_configuration_dir() . '/applications/' . $resource->uuid;
+ if (str($source, '.')) {
+ $source = str($source)->replaceFirst('.', $dir);
+ }
+ if (str($source, '~')) {
+ $source = str($source)->replaceFirst('~', $dir);
+ }
+ if ($pull_request_id !== 0) {
+ $source = $source . "-pr-$pull_request_id";
+ }
+ if ($read_only) {
+ data_set($volume, 'source', $source . ':' . $target . ':ro');
} else {
- data_set($volume, 'source', $source);
+ data_set($volume, 'source', $source . ':' . $target);
+ }
+ } else {
+ if ($pull_request_id !== 0) {
+ $source = $source . "-pr-$pull_request_id";
+ }
+ if ($read_only) {
+ data_set($volume, 'source', $source . ':' . $target . ':ro');
+ } else {
+ data_set($volume, 'source', $source . ':' . $target);
+ }
+ if (!str($source)->startsWith('/')) {
$topLevelVolumes->put($source, [
'name' => $source,
]);
}
}
}
- return $volume->value();
- });
- data_set($service, 'volumes', $serviceVolumes->toArray());
- }
+ }
+ if (is_array($volume)) {
+ return data_get($volume, 'source');
+ }
+ return $volume->value();
+ });
+ data_set($service, 'volumes', $serviceVolumes->toArray());
}
+
// Decide if the service is a database
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
data_set($service, 'is_database', $isDatabase);
@@ -1602,6 +1613,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $service;
});
+ if ($pull_request_id !== 0) {
+ $services->each(function ($service, $serviceName) use ($pull_request_id, $services) {
+ $services[$serviceName . "-pr-$pull_request_id"] = $service;
+ data_forget($services, $serviceName);
+ });
+ }
$finalServices = [
'version' => $dockerComposeVersion,
'services' => $services->toArray(),
diff --git a/config/coolify.php b/config/coolify.php
index 69ec23146..a6d6d8581 100644
--- a/config/coolify.php
+++ b/config/coolify.php
@@ -12,4 +12,6 @@ return [
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
+ 'is_horizon_enabled' => env('HORIZON_ENABLED', true),
+ 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true),
];
diff --git a/config/sentry.php b/config/sentry.php
index 5d46ba7f3..b4c1aa5f4 100644
--- a/config/sentry.php
+++ b/config/sentry.php
@@ -7,7 +7,7 @@ return [
// 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.240',
+ 'release' => '4.0.0-beta.241',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),
diff --git a/config/version.php b/config/version.php
index c7e1008fc..f00cd2d57 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,3 +1,3 @@
/etc/php/current_version/cli/conf.d/upload-limits.ini
\ No newline at end of file
+ } > /etc/php/current_version/cli/conf.d/upload-limits.ini
diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/run b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/run
index cec4f1dda..87471097e 100644
--- a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/run
+++ b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/run
@@ -1,5 +1,5 @@
#!/command/execlineb -P
foreground {
s6-sleep 5
- su - webuser -c "php /var/www/html/artisan horizon"
+ su - webuser -c "php /var/www/html/artisan start:horizon"
}
diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run
index 0f205c897..87ca0cae1 100644
--- a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run
+++ b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run
@@ -1,5 +1,5 @@
#!/command/execlineb -P
foreground {
s6-sleep 5
- su - webuser -c "php /var/www/html/artisan schedule:work"
+ su - webuser -c "php /var/www/html/artisan start:scheduler"
}
diff --git a/docker/prod-ssu/Dockerfile b/docker/prod-ssu/Dockerfile
index d5ba465b7..7d0a4d373 100644
--- a/docker/prod-ssu/Dockerfile
+++ b/docker/prod-ssu/Dockerfile
@@ -4,7 +4,7 @@ WORKDIR /var/www/html
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist
-FROM node:19 as static-assets
+FROM node:20 as static-assets
WORKDIR /app
COPY . .
COPY --from=base --chown=9999:9999 /var/www/html .
@@ -15,7 +15,7 @@ FROM serversideup/php:8.2-fpm-nginx
ARG TARGETPLATFORM
# https://github.com/cloudflare/cloudflared/releases
-ARG CLOUDFLARED_VERSION=2023.10.0
+ARG CLOUDFLARED_VERSION=2024.2.1
ARG POSTGRES_VERSION=15
WORKDIR /var/www/html
@@ -66,4 +66,4 @@ RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
RUN { \
echo 'upload_max_filesize=256M'; \
echo 'post_max_size=256M'; \
- } > /etc/php/current_version/cli/conf.d/upload-limits.ini
\ No newline at end of file
+ } > /etc/php/current_version/cli/conf.d/upload-limits.ini
diff --git a/package-lock.json b/package-lock.json
index b2124e9df..cc9889a18 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,6 +5,7 @@
"packages": {
"": {
"dependencies": {
+ "@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.5",
"daisyui": "4.7.2",
@@ -484,6 +485,17 @@
"node": ">= 8"
}
},
+ "node_modules/@tailwindcss/forms": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
+ "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==",
+ "dependencies": {
+ "mini-svg-data-uri": "^1.2.3"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
+ }
+ },
"node_modules/@tailwindcss/typography": {
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
@@ -1462,6 +1474,14 @@
"node": ">= 0.6"
}
},
+ "node_modules/mini-svg-data-uri": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
+ "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
+ "bin": {
+ "mini-svg-data-uri": "cli.js"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
diff --git a/package.json b/package.json
index 8568525e6..69c5c560a 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"vue": "3.4.21"
},
"dependencies": {
+ "@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.5",
"daisyui": "4.7.2",
diff --git a/resources/css/app.css b/resources/css/app.css
index 167b7c44a..6ac74f8a3 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -2,22 +2,141 @@
@tailwind components;
@tailwind utilities;
-html {
- @apply text-neutral-400;
+html,
+body {
+ @apply text-black bg-white dark:bg-base dark:text-neutral-400;
}
body {
@apply text-sm antialiased scrollbar;
}
-button[isError] {
+button[isError]:not(:disabled) {
@apply bg-red-600 hover:bg-red-700;
}
-button[isHighlighted] {
+button[isHighlighted]:not(:disabled) {
@apply bg-coollabs hover:bg-coollabs-100;
}
+.button {
+ @apply px-3 py-1.5 text-sm font-normal normal-case rounded dark:bg-coolgray-200 dark:text-white dark:hover:bg-coolgray-100 dark:disabled:bg-coolgray-100/40 dark:disabled:text-neutral-800 min-w-fit flex items-center justify-center;
+}
+
+h1 {
+ @apply text-2xl font-bold dark:text-white text-neutral-800;
+}
+
+h2 {
+ @apply text-xl font-bold dark:text-white text-neutral-800;
+}
+
+h3 {
+ @apply text-lg font-bold dark:text-white text-neutral-800;
+}
+
+h4 {
+ @apply text-base font-bold dark:text-white text-neutral-800;
+}
+
+a {
+ @apply dark:hover:text-white dark:text-neutral-400 text-neutral-600;
+}
+
+label {
+ @apply dark:text-neutral-400 text-neutral-600;
+}
+
+table {
+ @apply min-w-full divide-y divide-coolgray-200;
+}
+
+thead {
+ @apply uppercase;
+}
+
+tbody {
+ @apply divide-y divide-coolgray-200;
+}
+
+tr {
+ @apply text-neutral-400;
+}
+
+tr th {
+ @apply px-3 py-3.5 text-left text-white;
+}
+
+tr th:first-child {
+ @apply py-3.5 pl-4 pr-3 sm:pl-6;
+}
+
+tr td {
+ @apply px-3 py-4 whitespace-nowrap;
+}
+
+tr td:first-child {
+ @apply pl-4 pr-3 font-bold sm:pl-6;
+}
+
+input {
+ @apply pr-10;
+}
+.input {
+ @apply block w-full py-1.5 rounded border-0 text-sm ring-inset ring-1 dark:bg-coolgray-100 dark:text-white text-black focus:ring-2 dark:focus:ring-coolgray-300 dark:ring-coolgray-300 dark:read-only:text-neutral-500 dark:read-only:ring-0 dark:read-only:bg-coolgray-100/40 dark:placeholder:text-neutral-700;
+}
+
+option {
+ @apply text-white;
+}
+.alert-success {
+ @apply flex items-center gap-2 text-success;
+}
+.alert-error {
+ @apply flex items-center gap-2 text-error;
+}
+.dropdown-item {
+ @apply relative flex cursor-pointer select-none dark:hover:text-white dark:hover:bg-coollabs items-center px-2 py-1.5 text-xs justify-center outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 gap-2
+}
+
+.badge {
+ @apply inline-block w-3 h-3 text-xs font-bold leading-none border border-black rounded-full;
+}
+
+.badge-success {
+ @apply bg-success;
+}
+
+.badge-warning {
+ @apply bg-warning;
+}
+
+.badge-error {
+ @apply bg-error;
+}
+
+
+
+[type='checkbox']:checked {
+ background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
+}
+
+.menu {
+ @apply flex items-center gap-1;
+}
+
+.menu-item {
+ @apply flex items-center w-full gap-2 px-4 py-1 min-w-48 hover:bg-coolgray-100 dark:hover:text-white;
+}
+
+.menu-item-active {
+ @apply rounded-none dark:bg-coolgray-200 dark:text-warning;
+}
+
+.icon {
+ @apply w-4 h-4;
+}
+
.scrollbar {
@apply scrollbar-thumb-coollabs-100 scrollbar-track-coolgray-200 scrollbar-w-2;
}
@@ -27,12 +146,7 @@ button[isHighlighted] {
}
.custom-modal {
- @apply flex flex-col gap-2 px-8 py-4 border bg-base-100 border-coolgray-200;
-}
-
-.label-text,
-label {
- @apply text-neutral-400;
+ @apply z-50 flex flex-col gap-2 px-8 py-4 border dark:bg-coolgray-100 dark:border-coolgray-200;
}
.navbar-main {
@@ -43,26 +157,6 @@ label {
@apply w-4 text-warning;
}
-h1 {
- @apply text-3xl font-bold text-white;
-}
-
-h2 {
- @apply text-2xl font-bold text-white;
-}
-
-h3 {
- @apply text-xl font-bold text-white;
-}
-
-h4 {
- @apply text-base font-bold text-white;
-}
-
-a {
- @apply text-neutral-400 hover:text-white link link-hover hover:bg-transparent;
-}
-
.kbd-custom {
@apply px-2 text-xs border border-dashed rounded border-neutral-700 text-warning;
}
@@ -125,42 +219,13 @@ a {
@apply inline-block font-bold text-warning;
}
-table {
- @apply min-w-full divide-y divide-coolgray-200;
-}
-
-thead {
- @apply uppercase;
-}
-
-tbody {
- @apply divide-y divide-coolgray-200;
-}
-
-tr {
- @apply text-neutral-400;
-}
-
-tr th {
- @apply px-3 py-3.5 text-left text-white;
-}
-
-tr th:first-child {
- @apply py-3.5 pl-4 pr-3 sm:pl-6;
-}
-
-tr td {
- @apply px-3 py-4 whitespace-nowrap;
-}
-
-tr td:first-child {
- @apply pl-4 pr-3 font-bold sm:pl-6;
-}
-
.buyme {
@apply block px-3 py-2 mt-10 text-sm font-semibold leading-6 text-center text-white rounded-md shadow-sm bg-coolgray-200 hover:bg-coolgray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coolgray-200 hover:no-underline;
}
+.title {
+ @apply hidden pb-0 lg:block lg:pb-8 ;
+}
.subtitle {
@apply pt-2 pb-10;
}
@@ -169,13 +234,7 @@ tr td:first-child {
@apply fixed top-0 left-0 w-full h-full z-[9999] bg-coolgray-100 overflow-y-auto scrollbar pb-4;
}
-input.input-sm {
- @apply pr-10;
-}
-option{
- @apply text-white;
-}
-
.toast {
z-index: 1;
}
+
diff --git a/resources/js/components/MagicBar.vue b/resources/js/components/MagicBar.vue
index 386a267b1..7fe770279 100644
--- a/resources/js/components/MagicBar.vue
+++ b/resources/js/components/MagicBar.vue
@@ -9,6 +9,7 @@