This commit is contained in:
Eirik Mo 2024-04-02 21:07:50 +02:00
commit 38c20f5737
324 changed files with 6000 additions and 4540 deletions

View File

@ -21,6 +21,6 @@ body:
- type: input - type: input
attributes: attributes:
label: Version label: Version
description: Coolify's version (see bottom left corner). description: Coolify's version (see top of your screen).
validations: validations:
required: true required: true

View File

@ -18,15 +18,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v3 uses: docker/build-push-action@v5
with: with:
no-cache: true no-cache: true
context: . context: .
@ -40,15 +40,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v3 uses: docker/build-push-action@v5
with: with:
no-cache: true no-cache: true
context: . context: .
@ -64,13 +64,13 @@ jobs:
needs: [ amd64, aarch64 ] needs: [ amd64, aarch64 ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}

View File

@ -18,15 +18,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v3 uses: docker/build-push-action@v5
with: with:
no-cache: true no-cache: true
context: . context: .
@ -40,15 +40,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v3 uses: docker/build-push-action@v5
with: with:
no-cache: true no-cache: true
context: . context: .
@ -64,13 +64,13 @@ jobs:
needs: [ amd64, aarch64 ] needs: [ amd64, aarch64 ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}

View File

@ -18,15 +18,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v3 uses: docker/build-push-action@v5
with: with:
no-cache: true no-cache: true
context: . context: .
@ -40,15 +40,15 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry - name: Build image and push to registry
uses: docker/build-push-action@v3 uses: docker/build-push-action@v5
with: with:
no-cache: true no-cache: true
context: . context: .
@ -64,13 +64,13 @@ jobs:
needs: [ amd64, aarch64 ] needs: [ amd64, aarch64 ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}

View File

@ -14,7 +14,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Cache Docker layers - name: Cache Docker layers
uses: actions/cache@v2 uses: actions/cache@v2
with: with:

View File

@ -1,22 +0,0 @@
<?php
use App\Models\User;
$email = 'test@example.com';
$user = User::whereEmail($email)->first();
$teams = $user->teams;
foreach ($teams as $team) {
$servers = $team->servers;
if ($servers->count() > 0) {
foreach ($servers as $server) {
dump($server);
$server->delete();
}
}
dump($team);
$team->delete();
}
if ($user) {
dump($user);
$user->delete();
}

View File

@ -1,28 +0,0 @@
<?php
/**
* @label Send Email
* @description Send email to all users
*/
use App\Models\User;
use Illuminate\Support\Facades\Mail;
set_transanctional_email_settings();
$users = User::whereEmail('test@example.com');
foreach ($users as $user) {
Mail::send([], [], function ($message) use ($user) {
$message
->to($user->email)
->subject("Testing")
->text(
<<<EOF
Hello,
Welcome to Coolify Cloud.
Here is your user id: $user->id
EOF
);
});
}

View File

@ -30,5 +30,5 @@ Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if y
Mails are caught by Mailpit: `localhost:8025` Mails are caught by Mailpit: `localhost:8025`
## New Service Contribution ## New Service Contribution
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service). Check out the docs [here](https://coolify.io/docs/resources/services/add-service).

View File

@ -28,15 +28,16 @@ https://coolify.io/sponsorships
Thank you so much! Thank you so much!
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)! Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a> <a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
## Github Sponsors ($40+) ## Github Sponsors ($40+)
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a> <a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a> <a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a> <a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Corentin Clichy" /></a> <a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Corentin Clichy" /></a>
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a> <a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
<a href="https://github.com/Niki2k1"><img src="https://github.com/Niki2k1.png" width="60px" alt="Niklas Lausch" /></a> <a href="https://github.com/Niki2k1"><img src="https://github.com/Niki2k1.png" width="60px" alt="Niklas Lausch" /></a>

View File

@ -25,7 +25,8 @@ class StartDatabaseProxy
$proxyContainerName = "{$database->uuid}-proxy"; $proxyContainerName = "{$database->uuid}-proxy";
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') { if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
$databaseType = $database->databaseType(); $databaseType = $database->databaseType();
$network = data_get($database, 'service.destination.network'); // $connectPredefined = data_get($database, 'service.connect_to_docker_network');
$network = $database->service->uuid;
$server = data_get($database, 'service.destination.server'); $server = data_get($database, 'service.destination.server');
$proxyContainerName = "{$database->service->uuid}-proxy"; $proxyContainerName = "{$database->service->uuid}-proxy";
switch ($databaseType) { switch ($databaseType) {
@ -124,6 +125,7 @@ class StartDatabaseProxy
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2)); $dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
$nginxconf_base64 = base64_encode($nginxconf); $nginxconf_base64 = base64_encode($nginxconf);
$dockerfile_base64 = base64_encode($dockerfile); $dockerfile_base64 = base64_encode($dockerfile);
instant_remote_process(["docker rm -f $proxyContainerName"], $server, false);
instant_remote_process([ instant_remote_process([
"mkdir -p $configuration_dir", "mkdir -p $configuration_dir",
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile", "echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",

View File

@ -17,10 +17,12 @@ class StopDatabaseProxy
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database) public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
{ {
$server = data_get($database, 'destination.server'); $server = data_get($database, 'destination.server');
$uuid = $database->uuid;
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') { if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
$uuid = $database->service->uuid;
$server = data_get($database, 'service.server'); $server = data_get($database, 'service.server');
} }
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $server); instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
$database->is_public = false; $database->is_public = false;
$database->save(); $database->save();
} }

View File

@ -35,6 +35,9 @@ class CheckProxy
$server->save(); $server->save();
return false; return false;
} }
if ($server->settings->is_cloudflare_tunnel) {
return false;
}
$ip = $server->ip; $ip = $server->ip;
if ($server->id === 0) { if ($server->id === 0) {
$ip = 'host.docker.internal'; $ip = 'host.docker.internal';

View File

@ -0,0 +1,44 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class ConfigureCloudflared
{
use AsAction;
public function handle(Server $server, string $cloudflare_token)
{
try {
$config = [
"services" => [
"coolify-cloudflared" => [
"container_name" => "coolify-cloudflared",
"image" => "cloudflare/cloudflared:latest",
"restart" => RESTART_MODE,
"network_mode" => "host",
"command" => "tunnel run",
"environment" => [
"TUNNEL_TOKEN={$cloudflare_token}",
],
],
],
];
$config = Yaml::dump($config, 12, 2);
$docker_compose_yml_base64 = base64_encode($config);
$commands = collect([
"mkdir -p /tmp/cloudflared && cd /tmp/cloudflared",
"echo '$docker_compose_yml_base64' | base64 -d > docker-compose.yml",
"docker compose pull",
"docker compose down -v --remove-orphans > /dev/null 2>&1",
"docker compose up -d --remove-orphans",
]);
instant_remote_process($commands, $server);
} catch (\Throwable $e) {
ray($e);
throw $e;
}
}
}

View File

@ -13,7 +13,7 @@ class InstallDocker
{ {
$supported_os_type = $server->validateOS(); $supported_os_type = $server->validateOS();
if (!$supported_os_type) { if (!$supported_os_type) {
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/servers#install-docker-engine-manually">documentation</a>.'); throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.');
} }
ray('Installing Docker on server: ' . $server->name . ' (' . $server->ip . ')' . ' with OS type: ' . $supported_os_type); ray('Installing Docker on server: ' . $server->name . ' (' . $server->ip . ')' . ' with OS type: ' . $supported_os_type);
$dockerVersion = '24.0'; $dockerVersion = '24.0';

View File

@ -12,10 +12,12 @@ class UpdateCoolify
public ?Server $server = null; public ?Server $server = null;
public ?string $latestVersion = null; public ?string $latestVersion = null;
public ?string $currentVersion = null; public ?string $currentVersion = null;
public bool $async = false;
public function handle(bool $force) public function handle(bool $force = false, bool $async = false)
{ {
try { try {
$this->async = $async;
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob'); ray('Running InstanceAutoUpdateJob');
$this->server = Server::find(0); $this->server = Server::find(0);
@ -56,17 +58,31 @@ class UpdateCoolify
{ {
if (isDev()) { if (isDev()) {
ray("Running update on local docker container. Updating to $this->latestVersion"); ray("Running update on local docker container. Updating to $this->latestVersion");
if ($this->async) {
ray('Running async update');
remote_process([ remote_process([
"sleep 10" "sleep 10"
], $this->server); ], $this->server);
} else {
instant_remote_process([
"sleep 10"
], $this->server);
}
ray('Update done'); ray('Update done');
return; return;
} else { } else {
ray('Running update on production server'); ray('Running update on production server');
if ($this->async) {
remote_process([ remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh", "curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion" "bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server); ], $this->server);
} else {
instant_remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server);
}
return; return;
} }
} }

View File

@ -20,7 +20,7 @@ class CleanupDatabase extends Command
$keep_days = 60; $keep_days = 60;
echo "Keep days: $keep_days\n"; echo "Keep days: $keep_days\n";
// Cleanup failed jobs table // Cleanup failed jobs table
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(7)); $failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));
$count = $failed_jobs->count(); $count = $failed_jobs->count();
echo "Delete $count entries from failed_jobs.\n"; echo "Delete $count entries from failed_jobs.\n";
if ($this->option('yes')) { if ($this->option('yes')) {

View File

@ -0,0 +1,21 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Horizon extends Command
{
protected $signature = 'start:horizon';
protected $description = 'Start Horizon';
public function handle()
{
if (config('coolify.is_horizon_enabled')) {
$this->info('Horizon is enabled. Starting.');
$this->call('horizon');
exit(0);
} else {
exit(0);
}
}
}

View File

@ -34,12 +34,14 @@ class Init extends Command
$this->cleanup_stucked_helper_containers(); $this->cleanup_stucked_helper_containers();
$this->call('cleanup:queue'); $this->call('cleanup:queue');
$this->call('cleanup:stucked-resources'); $this->call('cleanup:stucked-resources');
if (!isCloud()) {
try { try {
$server = Server::find(0)->first(); $server = Server::find(0)->first();
$server->setupDynamicProxyConfiguration(); $server->setupDynamicProxyConfiguration();
} catch (\Throwable $e) { } catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n"; echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
} }
}
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
if (!is_null(env('AUTOUPDATE', null))) { if (!is_null(env('AUTOUPDATE', null))) {

View File

@ -0,0 +1,21 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Scheduler extends Command
{
protected $signature = 'start:scheduler';
protected $description = 'Start Scheduler';
public function handle()
{
if (config('coolify.is_scheduler_enabled')) {
$this->info('Scheduler is enabled. Starting.');
$this->call('schedule:work');
exit(0);
} else {
exit(0);
}
}
}

View File

@ -48,11 +48,9 @@ class Kernel extends ConsoleKernel
$this->pull_helper_image($schedule); $this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule); $this->check_scheduled_tasks($schedule);
if (!isCloud()) {
$schedule->command('cleanup:database --yes')->daily(); $schedule->command('cleanup:database --yes')->daily();
} }
} }
}
private function pull_helper_image($schedule) private function pull_helper_image($schedule)
{ {
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); $servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
@ -72,43 +70,14 @@ class Kernel extends ConsoleKernel
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false); $containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
} }
foreach ($containerServers as $server) { foreach ($containerServers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer(); $schedule->job(new ContainerStatusJob($server))->everyTwoMinutes()->onOneServer();
if ($server->isLogDrainEnabled()) { if ($server->isLogDrainEnabled()) {
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer(); $schedule->job(new CheckLogDrainContainerJob($server))->everyTwoMinutes()->onOneServer();
} }
} }
foreach ($servers as $server) { foreach ($servers as $server) {
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer(); $schedule->job(new ServerStatusJob($server))->everyTwoMinutes()->onOneServer();
} }
// Delayed Jobs
// foreach ($containerServers as $server) {
// $schedule
// ->call(function () use ($server) {
// $randomSeconds = rand(1, 40);
// $job = new ContainerStatusJob($server);
// $job->delay($randomSeconds);
// ray('dispatching container status job in ' . $randomSeconds . ' seconds');
// dispatch($job);
// })->name('container-status-' . $server->id)->everyMinute()->onOneServer();
// if ($server->isLogDrainEnabled()) {
// $schedule
// ->call(function () use ($server) {
// $randomSeconds = rand(1, 40);
// $job = new CheckLogDrainContainerJob($server);
// $job->delay($randomSeconds);
// dispatch($job);
// })->name('log-drain-container-check-' . $server->id)->everyMinute()->onOneServer();
// }
// }
// foreach ($servers as $server) {
// $schedule
// ->call(function () use ($server) {
// $randomSeconds = rand(1, 40);
// $job = new ServerStatusJob($server);
// $job->delay($randomSeconds);
// dispatch($job);
// })->name('server-status-job-' . $server->id)->everyMinute()->onOneServer();
// }
} }
private function instance_auto_update($schedule) private function instance_auto_update($schedule)
{ {

View File

@ -44,7 +44,7 @@ class Deploy extends Controller
$force = $request->query->get('force') ?? false; $force = $request->query->get('force') ?? false;
if ($uuids && $tags) { if ($uuids && $tags) {
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400); return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
} }
if (is_null($teamId)) { if (is_null($teamId)) {
return invalid_token(); return invalid_token();
@ -54,7 +54,7 @@ class Deploy extends Controller
} else if ($uuids) { } else if ($uuids) {
return $this->by_uuids($uuids, $teamId, $force); return $this->by_uuids($uuids, $teamId, $force);
} }
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400); return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
} }
private function by_uuids(string $uuid, int $teamId, bool $force = false) private function by_uuids(string $uuid, int $teamId, bool $force = false)
{ {
@ -62,7 +62,7 @@ class Deploy extends Controller
$uuids = collect(array_filter($uuids)); $uuids = collect(array_filter($uuids));
if (count($uuids) === 0) { if (count($uuids) === 0) {
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400); return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
} }
$deployments = collect(); $deployments = collect();
$payload = collect(); $payload = collect();
@ -81,7 +81,7 @@ class Deploy extends Controller
$payload->put('deployments', $deployments->toArray()); $payload->put('deployments', $deployments->toArray());
return response()->json($payload->toArray(), 200); return response()->json($payload->toArray(), 200);
} }
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-reference/deploy-webhook'], 404);
} }
public function by_tags(string $tags, int $team_id, bool $force = false) public function by_tags(string $tags, int $team_id, bool $force = false)
{ {
@ -89,7 +89,7 @@ class Deploy extends Controller
$tags = collect(array_filter($tags)); $tags = collect(array_filter($tags));
if (count($tags) === 0) { if (count($tags) === 0) {
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400); return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
} }
$message = collect([]); $message = collect([]);
$deployments = collect(); $deployments = collect();
@ -127,7 +127,7 @@ class Deploy extends Controller
return response()->json($payload->toArray(), 200); return response()->json($payload->toArray(), 200);
} }
return response()->json(['error' => "No resources found with this tag.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404); return response()->json(['error' => "No resources found with this tag.", 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
} }
public function deploy_resource($resource, bool $force = false): array public function deploy_resource($resource, bool $force = false): array
{ {

View File

@ -26,7 +26,7 @@ class Team extends Controller
$teams = auth()->user()->teams; $teams = auth()->user()->teams;
$team = $teams->where('id', $id)->first(); $team = $teams->where('id', $id)->first();
if (is_null($team)) { if (is_null($team)) {
return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api/team-by-id"], 404); return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api-reference/get-team-by-teamid"], 404);
} }
return response()->json($team); return response()->json($team);
} }
@ -40,7 +40,7 @@ class Team extends Controller
$teams = auth()->user()->teams; $teams = auth()->user()->teams;
$team = $teams->where('id', $id)->first(); $team = $teams->where('id', $id)->first();
if (is_null($team)) { if (is_null($team)) {
return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api/team-by-id-members"], 404); return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api-reference/get-team-by-teamid-members"], 404);
} }
return response()->json($team->members); return response()->json($team->members);
} }

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class OauthController extends Controller {
public function redirect(string $provider)
{
$socialite_provider = get_socialite_provider($provider);
return $socialite_provider->redirect();
}
public function callback(string $provider)
{
try {
$oauthUser = get_socialite_provider($provider)->user();
$user = User::whereEmail($oauthUser->email)->first();
if (!$user) {
$user = User::create([
'name' => $oauthUser->name,
'email' => $oauthUser->email,
]);
}
Auth::login($user);
return redirect('/');
} catch (\Exception $e) {
ray($e->getMessage());
return redirect()->route('login')->withErrors([__('auth.failed.callback')]);
}
}
}

View File

@ -298,6 +298,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"ignore_errors" => true, "ignore_errors" => true,
] ]
); );
// $this->execute_remote_command( // $this->execute_remote_command(
// [ // [
// "docker image prune -f >/dev/null 2>&1", // "docker image prune -f >/dev/null 2>&1",
@ -305,6 +307,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// "ignore_errors" => true, // "ignore_errors" => true,
// ] // ]
// ); // );
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id')); 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 "docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
]); ]);
} }
$this->write_deployment_configurations();
// Start compose file // Start compose file
if ($this->application->settings->is_raw_compose_deployment_enabled) { if ($this->application->settings->is_raw_compose_deployment_enabled) {
@ -425,7 +428,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), "hidden" => true], [executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
); );
$this->write_deployment_configurations();
} else { } else {
$this->write_deployment_configurations();
$server_workdir = $this->application->workdir(); $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"); ray("SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
$this->execute_remote_command( $this->execute_remote_command(
@ -437,14 +442,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true], [executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
); );
$this->write_deployment_configurations();
} else { } else {
$this->execute_remote_command( $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], [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."); $this->application_deployment_queue->addLogEntry("New container started.");
} }
private function deploy_dockerfile_buildpack() private function deploy_dockerfile_buildpack()
@ -731,7 +737,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first(); $nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
} }
if ($nixpacks_php_fallback_path?->value === '/index.php' && $nixpacks_php_root_dir?->value === '/app/public' && $this->newVersionIsHealthy === false) { if ($nixpacks_php_fallback_path?->value === '/index.php' && $nixpacks_php_root_dir?->value === '/app/public' && $this->newVersionIsHealthy === false) {
$this->application_deployment_queue->addLogEntry("There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/frameworks/laravel#requirements", 'stderr'); $this->application_deployment_queue->addLogEntry("There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/resources/laravel", 'stderr');
} }
} }
private function rolling_update() private function rolling_update()
@ -822,6 +828,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
private function deploy_pull_request() private function deploy_pull_request()
{ {
if ($this->application->build_pack === 'dockercompose') {
$this->deploy_docker_compose_buildpack();
return;
}
if ($this->use_build_server) { if ($this->use_build_server) {
$this->server = $this->build_server; $this->server = $this->build_server;
} }
@ -888,6 +898,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->application->additional_networks->count() === 0) { if ($this->application->additional_networks->count() === 0) {
return; return;
} }
if ($this->pull_request_id !== 0) {
return;
}
$destination_ids = $this->application->additional_networks->pluck('id'); $destination_ids = $this->application->additional_networks->pluck('id');
if ($this->server->isSwarm()) { if ($this->server->isSwarm()) {
$this->application_deployment_queue->addLogEntry("Additional destinations are not supported in swarm mode."); $this->application_deployment_queue->addLogEntry("Additional destinations are not supported in swarm mode.");

View File

@ -8,6 +8,7 @@ use App\Actions\Proxy\StartProxy;
use App\Actions\Shared\ComplexStatusCheck; use App\Actions\Shared\ComplexStatusCheck;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use App\Models\Server; use App\Models\Server;
use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted; use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped; use App\Notifications\Container\ContainerStopped;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
@ -149,7 +150,32 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
} }
} else { } else {
$uuid = data_get($labels, 'com.docker.compose.service'); $uuid = data_get($labels, 'com.docker.compose.service');
$type = data_get($labels, 'coolify.type');
if ($uuid) { if ($uuid) {
if ($type === 'service') {
$database_id = data_get($labels, 'coolify.service.subId');
if ($database_id) {
$service_db = ServiceDatabase::where('id', $database_id)->first();
if ($service_db) {
$uuid = $service_db->service->uuid;
$isPublic = data_get($service_db, 'is_public');
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (!$foundTcpProxy) {
StartDatabaseProxy::run($service_db);
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
}
}
}
}
} else {
$database = $databases->where('uuid', $uuid)->first(); $database = $databases->where('uuid', $uuid)->first();
if ($database) { if ($database) {
$isPublic = data_get($database, 'is_public'); $isPublic = data_get($database, 'is_public');
@ -175,6 +201,8 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
// Notify user that this container should not be there. // Notify user that this container should not be there.
} }
} }
}
if (data_get($container, 'Name') === '/coolify-db') { if (data_get($container, 'Name') === '/coolify-db') {
$foundDatabases[] = 0; $foundDatabases[] = 0;
} }

View File

@ -95,7 +95,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$databaseType = $this->database->databaseType(); $databaseType = $this->database->databaseType();
$serviceUuid = $this->database->service->uuid; $serviceUuid = $this->database->service->uuid;
$serviceName = str($this->database->service->name)->slug(); $serviceName = str($this->database->service->name)->slug();
if ($databaseType === 'standalone-postgresql') { if (str($databaseType)->contains('postgres')) {
$this->container_name = "{$this->database->name}-$serviceUuid"; $this->container_name = "{$this->database->name}-$serviceUuid";
$this->directory_name = $serviceName . '-' . $this->container_name; $this->directory_name = $serviceName . '-' . $this->container_name;
$commands[] = "docker exec $this->container_name env | grep POSTGRES_"; $commands[] = "docker exec $this->container_name env | grep POSTGRES_";
@ -120,7 +120,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$databasesToBackup = $this->database->postgres_user; $databasesToBackup = $this->database->postgres_user;
} }
} else if ($databaseType === 'standalone-mysql') { } else if (str($databaseType)->contains('mysql')) {
$this->container_name = "{$this->database->name}-$serviceUuid"; $this->container_name = "{$this->database->name}-$serviceUuid";
$this->directory_name = $serviceName . '-' . $this->container_name; $this->directory_name = $serviceName . '-' . $this->container_name;
$commands[] = "docker exec $this->container_name env | grep MYSQL_"; $commands[] = "docker exec $this->container_name env | grep MYSQL_";
@ -143,7 +143,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
throw new \Exception('MYSQL_DATABASE not found'); throw new \Exception('MYSQL_DATABASE not found');
} }
} else if ($databaseType === 'standalone-mariadb') { } else if (str($databaseType)->contains('mariadb')) {
$this->container_name = "{$this->database->name}-$serviceUuid"; $this->container_name = "{$this->database->name}-$serviceUuid";
$this->directory_name = $serviceName . '-' . $this->container_name; $this->directory_name = $serviceName . '-' . $this->container_name;
$commands[] = "docker exec $this->container_name env"; $commands[] = "docker exec $this->container_name env";
@ -190,32 +190,32 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
} }
if (is_null($databasesToBackup)) { if (is_null($databasesToBackup)) {
if ($databaseType === 'standalone-postgresql') { if (str($databaseType)->contains('postgres')) {
$databasesToBackup = [$this->database->postgres_db]; $databasesToBackup = [$this->database->postgres_db];
} else if ($databaseType === 'standalone-mongodb') { } else if (str($databaseType)->contains('mongodb')) {
$databasesToBackup = ['*']; $databasesToBackup = ['*'];
} else if ($databaseType === 'standalone-mysql') { } else if (str($databaseType)->contains('mysql')) {
$databasesToBackup = [$this->database->mysql_database]; $databasesToBackup = [$this->database->mysql_database];
} else if ($databaseType === 'standalone-mariadb') { } else if (str($databaseType)->contains('mariadb')) {
$databasesToBackup = [$this->database->mariadb_database]; $databasesToBackup = [$this->database->mariadb_database];
} else { } else {
return; return;
} }
} else { } else {
if ($databaseType === 'standalone-postgresql') { if (str($databaseType)->contains('postgres')) {
// Format: db1,db2,db3 // Format: db1,db2,db3
$databasesToBackup = explode(',', $databasesToBackup); $databasesToBackup = explode(',', $databasesToBackup);
$databasesToBackup = array_map('trim', $databasesToBackup); $databasesToBackup = array_map('trim', $databasesToBackup);
} else if ($databaseType === 'standalone-mongodb') { } else if (str($databaseType)->contains('mongodb')) {
// Format: db1:collection1,collection2|db2:collection3,collection4 // Format: db1:collection1,collection2|db2:collection3,collection4
$databasesToBackup = explode('|', $databasesToBackup); $databasesToBackup = explode('|', $databasesToBackup);
$databasesToBackup = array_map('trim', $databasesToBackup); $databasesToBackup = array_map('trim', $databasesToBackup);
ray($databasesToBackup); ray($databasesToBackup);
} else if ($databaseType === 'standalone-mysql') { } else if (str($databaseType)->contains('mysql')) {
// Format: db1,db2,db3 // Format: db1,db2,db3
$databasesToBackup = explode(',', $databasesToBackup); $databasesToBackup = explode(',', $databasesToBackup);
$databasesToBackup = array_map('trim', $databasesToBackup); $databasesToBackup = array_map('trim', $databasesToBackup);
} else if ($databaseType === 'standalone-mariadb') { } else if (str($databaseType)->contains('mariadb')) {
// Format: db1,db2,db3 // Format: db1,db2,db3
$databasesToBackup = explode(',', $databasesToBackup); $databasesToBackup = explode(',', $databasesToBackup);
$databasesToBackup = array_map('trim', $databasesToBackup); $databasesToBackup = array_map('trim', $databasesToBackup);
@ -235,7 +235,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$size = 0; $size = 0;
ray('Backing up ' . $database); ray('Backing up ' . $database);
try { try {
if ($databaseType === 'standalone-postgresql') { if (str($databaseType)->contains('postgres')) {
$this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp"; $this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp";
$this->backup_location = $this->backup_dir . $this->backup_file; $this->backup_location = $this->backup_dir . $this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([ $this->backup_log = ScheduledDatabaseBackupExecution::create([
@ -244,7 +244,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
'scheduled_database_backup_id' => $this->backup->id, 'scheduled_database_backup_id' => $this->backup->id,
]); ]);
$this->backup_standalone_postgresql($database); $this->backup_standalone_postgresql($database);
} else if ($databaseType === 'standalone-mongodb') { } else if (str($databaseType)->contains('mongodb')) {
if ($database === '*') { if ($database === '*') {
$database = 'all'; $database = 'all';
$databaseName = 'all'; $databaseName = 'all';
@ -263,7 +263,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
'scheduled_database_backup_id' => $this->backup->id, 'scheduled_database_backup_id' => $this->backup->id,
]); ]);
$this->backup_standalone_mongodb($database); $this->backup_standalone_mongodb($database);
} else if ($databaseType === 'standalone-mysql') { } else if (str($databaseType)->contains('mysql')) {
$this->backup_file = "/mysql-dump-$database-" . Carbon::now()->timestamp . ".dmp"; $this->backup_file = "/mysql-dump-$database-" . Carbon::now()->timestamp . ".dmp";
$this->backup_location = $this->backup_dir . $this->backup_file; $this->backup_location = $this->backup_dir . $this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([ $this->backup_log = ScheduledDatabaseBackupExecution::create([
@ -272,7 +272,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
'scheduled_database_backup_id' => $this->backup->id, 'scheduled_database_backup_id' => $this->backup->id,
]); ]);
$this->backup_standalone_mysql($database); $this->backup_standalone_mysql($database);
} else if ($databaseType === 'standalone-mariadb') { } else if (str($databaseType)->contains('mariadb')) {
$this->backup_file = "/mariadb-dump-$database-" . Carbon::now()->timestamp . ".dmp"; $this->backup_file = "/mariadb-dump-$database-" . Carbon::now()->timestamp . ".dmp";
$this->backup_location = $this->backup_dir . $this->backup_file; $this->backup_location = $this->backup_dir . $this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([ $this->backup_log = ScheduledDatabaseBackupExecution::create([

View File

@ -23,6 +23,6 @@ class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncr
public function handle(): void public function handle(): void
{ {
UpdateCoolify::run($this->force); UpdateCoolify::run(force: $this->force, async: false);
} }
} }

View File

@ -13,6 +13,7 @@ class ActivityMonitor extends Component
public $activityId; public $activityId;
public $eventToDispatch = 'activityFinished'; public $eventToDispatch = 'activityFinished';
public $isPollingActive = false; public $isPollingActive = false;
public bool $showWaiting = false;
protected $activity; protected $activity;
protected $listeners = ['activityMonitor' => 'newMonitorActivity']; protected $listeners = ['activityMonitor' => 'newMonitorActivity'];

View File

@ -2,22 +2,24 @@
namespace App\Livewire\Boarding; namespace App\Livewire\Boarding;
use App\Actions\Server\InstallDocker;
use App\Enums\ProxyTypes; use App\Enums\ProxyTypes;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use App\Models\Project; use App\Models\Project;
use App\Models\Server; use App\Models\Server;
use App\Models\Team; use App\Models\Team;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
class Index extends Component class Index extends Component
{ {
protected $listeners = ['serverInstalled' => 'validateServer']; protected $listeners = ['serverInstalled' => 'validateServer'];
public string $currentState = 'welcome'; public string $currentState = 'welcome';
public ?string $selectedServerType = null; public ?string $selectedServerType = null;
public ?Collection $privateKeys = null; public ?Collection $privateKeys = null;
public ?int $selectedExistingPrivateKey = null; public ?int $selectedExistingPrivateKey = null;
public ?string $privateKeyType = null; public ?string $privateKeyType = null;
public ?string $privateKey = null; public ?string $privateKey = null;
@ -27,6 +29,7 @@ class Index extends Component
public ?PrivateKey $createdPrivateKey = null; public ?PrivateKey $createdPrivateKey = null;
public ?Collection $servers = null; public ?Collection $servers = null;
public ?int $selectedExistingServer = null; public ?int $selectedExistingServer = null;
public ?string $remoteServerName = null; public ?string $remoteServerName = null;
public ?string $remoteServerDescription = null; public ?string $remoteServerDescription = null;
@ -38,7 +41,8 @@ class Index extends Component
public ?Server $createdServer = null; public ?Server $createdServer = null;
public Collection $projects; public Collection $projects;
public ?int $selectedExistingProject = null;
public ?int $selectedProject = null;
public ?Project $createdProject = null; public ?Project $createdProject = null;
public bool $dockerInstallationStarted = false; public bool $dockerInstallationStarted = false;
@ -62,6 +66,26 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->remoteServerDescription = 'Created by Coolify'; $this->remoteServerDescription = 'Created by Coolify';
$this->remoteServerHost = 'coolify-testing-host'; $this->remoteServerHost = 'coolify-testing-host';
} }
// if ($this->currentState === 'create-project') {
// $this->getProjects();
// }
// if ($this->currentState === 'create-resource') {
// $this->selectExistingServer();
// $this->selectExistingProject();
// }
// if ($this->currentState === 'private-key') {
// $this->setServerType('remote');
// }
// if ($this->currentState === 'create-server') {
// $this->selectExistingPrivateKey();
// }
// if ($this->currentState === 'validate-server') {
// $this->selectExistingServer();
// }
// if ($this->currentState === 'select-existing-server') {
// $this->selectExistingServer();
// }
} }
public function explanation() public function explanation()
{ {
@ -89,6 +113,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->selectedServerType = $type; $this->selectedServerType = $type;
if ($this->selectedServerType === 'localhost') { if ($this->selectedServerType === 'localhost') {
$this->createdServer = Server::find(0); $this->createdServer = Server::find(0);
$this->selectedExistingServer = 0;
if (!$this->createdServer) { if (!$this->createdServer) {
return $this->dispatch('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.'); return $this->dispatch('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
} }
@ -137,6 +162,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
} }
public function selectExistingPrivateKey() public function selectExistingPrivateKey()
{ {
if (is_null($this->selectedExistingPrivateKey)) {
$this->restartBoarding();
return;
}
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey); $this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
$this->privateKey = $this->createdPrivateKey->private_key; $this->privateKey = $this->createdPrivateKey->private_key;
$this->currentState = 'create-server'; $this->currentState = 'create-server';
@ -196,6 +225,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel; $this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
$this->createdServer->settings->save(); $this->createdServer->settings->save();
$this->createdServer->addInitialNetwork(); $this->createdServer->addInitialNetwork();
$this->selectedExistingServer = $this->createdServer->id;
$this->currentState = 'validate-server'; $this->currentState = 'validate-server';
} }
public function installServer() public function installServer()
@ -249,13 +279,13 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{ {
$this->projects = Project::ownedByCurrentTeam(['name'])->get(); $this->projects = Project::ownedByCurrentTeam(['name'])->get();
if ($this->projects->count() > 0) { if ($this->projects->count() > 0) {
$this->selectedExistingProject = $this->projects->first()->id; $this->selectedProject = $this->projects->first()->id;
} }
$this->currentState = 'create-project'; $this->currentState = 'create-project';
} }
public function selectExistingProject() public function selectExistingProject()
{ {
$this->createdProject = Project::find($this->selectedExistingProject); $this->createdProject = Project::find($this->selectedProject);
$this->currentState = 'create-resource'; $this->currentState = 'create-resource';
} }
public function createNewProject() public function createNewProject()

View File

@ -3,6 +3,7 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use App\Models\PrivateKey;
use App\Models\Project; use App\Models\Project;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -13,9 +14,11 @@ class Dashboard extends Component
{ {
public $projects = []; public $projects = [];
public Collection $servers; public Collection $servers;
public Collection $private_keys;
public $deployments_per_server; public $deployments_per_server;
public function mount() public function mount()
{ {
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
$this->servers = Server::ownedByCurrentTeam()->get(); $this->servers = Server::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get(); $this->projects = Project::ownedByCurrentTeam()->get();
$this->get_deployments(); $this->get_deployments();

View File

@ -5,7 +5,7 @@ namespace App\Livewire\Destination\New;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker as ModelsStandaloneDocker; use App\Models\StandaloneDocker as ModelsStandaloneDocker;
use App\Models\SwarmDocker; use App\Models\SwarmDocker;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
@ -14,7 +14,7 @@ class Docker extends Component
public string $name; public string $name;
public string $network; public string $network;
public Collection $servers; public ?Collection $servers = null;
public Server $server; public Server $server;
public ?int $server_id = null; public ?int $server_id = null;
public bool $is_swarm = false; public bool $is_swarm = false;
@ -34,6 +34,9 @@ class Docker extends Component
public function mount() public function mount()
{ {
if (is_null($this->servers)) {
$this->servers = Server::isReachable()->get();
}
if (request()->query('server_id')) { if (request()->query('server_id')) {
$this->server_id = request()->query('server_id'); $this->server_id = request()->query('server_id');
} else { } else {
@ -46,8 +49,10 @@ class Docker extends Component
} else { } else {
$this->network = new Cuid2(7); $this->network = new Cuid2(7);
} }
if ($this->servers->count() > 0) {
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab(); $this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
} }
}
public function generate_name() public function generate_name()
{ {

View File

@ -3,6 +3,8 @@
namespace App\Livewire\Destination; namespace App\Livewire\Destination;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
@ -11,6 +13,40 @@ class Show extends Component
public Server $server; public Server $server;
public Collection|array $networks = []; public Collection|array $networks = [];
private function createNetworkAndAttachToProxy()
{
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
public function add($name)
{
if ($this->server->isSwarm()) {
$found = $this->server->swarmDockers()->where('network', $name)->first();
if ($found) {
$this->dispatch('error', 'Network already added to this server.');
return;
} else {
SwarmDocker::create([
'name' => $this->server->name . "-" . $name,
'network' => $this->name,
'server_id' => $this->server->id,
]);
}
} else {
$found = $this->server->standaloneDockers()->where('network', $name)->first();
if ($found) {
$this->dispatch('error', 'Network already added to this server.');
return;
} else {
StandaloneDocker::create([
'name' => $this->server->name . "-" . $name,
'network' => $name,
'server_id' => $this->server->id,
]);
}
$this->createNetworkAndAttachToProxy();
}
}
public function scan() public function scan()
{ {
if ($this->server->isSwarm()) { if ($this->server->isSwarm()) {
@ -26,6 +62,8 @@ class Show extends Component
}); });
if ($this->networks->count() === 0) { if ($this->networks->count() === 0) {
$this->dispatch('success', 'No new networks found.'); $this->dispatch('success', 'No new networks found.');
return;
} }
$this->dispatch('success', 'Scan done.');
} }
} }

View File

@ -17,14 +17,6 @@ class LayoutPopups extends Component
{ {
$this->dispatch('success', 'Realtime events configured!'); $this->dispatch('success', 'Realtime events configured!');
} }
public function disableSponsorship()
{
auth()->user()->update(['is_notification_sponsorship_enabled' => false]);
}
public function disableNotifications()
{
auth()->user()->update(['is_notification_notifications_enabled' => false]);
}
public function render() public function render()
{ {
return view('livewire.layout-popups'); return view('livewire.layout-popups');

View File

@ -6,7 +6,7 @@ use App\Models\Team;
use App\Notifications\Test; use App\Notifications\Test;
use Livewire\Component; use Livewire\Component;
class DiscordSettings extends Component class Discord extends Component
{ {
public Team $team; public Team $team;
protected $rules = [ protected $rules = [
@ -55,4 +55,8 @@ class DiscordSettings extends Component
$this->team?->notify(new Test()); $this->team?->notify(new Test());
$this->dispatch('success', 'Test notification sent.'); $this->dispatch('success', 'Test notification sent.');
} }
public function render()
{
return view('livewire.notifications.discord');
}
} }

View File

@ -2,13 +2,12 @@
namespace App\Livewire\Notifications; namespace App\Livewire\Notifications;
use Livewire\Component;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\Team; use App\Models\Team;
use App\Notifications\Test; use App\Notifications\Test;
use Livewire\Component;
use Log;
class EmailSettings extends Component class Email extends Component
{ {
public Team $team; public Team $team;
public string $emails; public string $emails;
@ -119,6 +118,7 @@ class EmailSettings extends Component
{ {
try { try {
$this->resetErrorBag(); $this->resetErrorBag();
if (!$this->team->use_instance_email_settings) {
$this->validate([ $this->validate([
'team.smtp_from_address' => 'required|email', 'team.smtp_from_address' => 'required|email',
'team.smtp_from_name' => 'required', 'team.smtp_from_name' => 'required',
@ -129,6 +129,7 @@ class EmailSettings extends Component
'team.smtp_password' => 'nullable', 'team.smtp_password' => 'nullable',
'team.smtp_timeout' => 'nullable', 'team.smtp_timeout' => 'nullable',
]); ]);
}
$this->team->save(); $this->team->save();
refreshSession(); refreshSession();
$this->dispatch('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
@ -189,4 +190,8 @@ class EmailSettings extends Component
} }
$this->dispatch('error', 'Instance SMTP/Resend settings are not enabled.'); $this->dispatch('error', 'Instance SMTP/Resend settings are not enabled.');
} }
public function render()
{
return view('livewire.notifications.email');
}
} }

View File

@ -6,8 +6,9 @@ use App\Models\Team;
use App\Notifications\Test; use App\Notifications\Test;
use Livewire\Component; use Livewire\Component;
class TelegramSettings extends Component class Telegram extends Component
{ {
public Team $team; public Team $team;
protected $rules = [ protected $rules = [
'team.telegram_enabled' => 'nullable|boolean', 'team.telegram_enabled' => 'nullable|boolean',
@ -61,4 +62,8 @@ class TelegramSettings extends Component
$this->team?->notify(new Test()); $this->team?->notify(new Test());
$this->dispatch('success', 'Test notification sent.'); $this->dispatch('success', 'Test notification sent.');
} }
public function render()
{
return view('livewire.notifications.telegram');
}
} }

View File

@ -11,11 +11,8 @@ class Index extends Component
public int $userId; public int $userId;
public string $email; public string $email;
#[Validate('required')]
public string $current_password; public string $current_password;
#[Validate('required|min:8')]
public string $new_password; public string $new_password;
#[Validate('required|min:8|same:new_password')]
public string $new_password_confirmation; public string $new_password_confirmation;
#[Validate('required')] #[Validate('required')]
@ -29,7 +26,9 @@ class Index extends Component
public function submit() public function submit()
{ {
try { try {
$this->validate(); $this->validate([
'name' => 'required',
]);
auth()->user()->update([ auth()->user()->update([
'name' => $this->name, 'name' => $this->name,
]); ]);
@ -42,7 +41,11 @@ class Index extends Component
public function resetPassword() public function resetPassword()
{ {
try { try {
$this->validate(); $this->validate([
'current_password' => 'required',
'new_password' => 'required|min:8',
'new_password_confirmation' => 'required|min:8|same:new_password',
]);
if (!Hash::check($this->current_password, auth()->user()->password)) { if (!Hash::check($this->current_password, auth()->user()->password)) {
$this->dispatch('error', 'Current password is incorrect.'); $this->dispatch('error', 'Current password is incorrect.');
return; return;

View File

@ -9,7 +9,7 @@ use Livewire\Component;
class Index extends Component class Index extends Component
{ {
public Application $application; public Application $application;
public array|Collection $deployments = []; public ?Collection $deployments;
public int $deployments_count = 0; public int $deployments_count = 0;
public string $current_url; public string $current_url;
public int $skip = 0; public int $skip = 0;
@ -48,9 +48,9 @@ class Index extends Component
} }
private function show_more() private function show_more()
{ {
if (count($this->deployments) !== 0) { if ($this->deployments->count() !== 0) {
$this->show_next = true; $this->show_next = true;
if (count($this->deployments) < $this->default_take) { if ($this->deployments->count() < $this->default_take) {
$this->show_next = false; $this->show_next = false;
} }
return; return;
@ -63,7 +63,6 @@ class Index extends Component
} }
public function previous_page(?int $take = null) public function previous_page(?int $take = null)
{ {
if ($take) { if ($take) {
$this->skip = $this->skip - $take; $this->skip = $this->skip - $take;
} }

View File

@ -251,7 +251,7 @@ class General extends Component
if ($this->application->additional_servers->count() === 0) { if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) { foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) { if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help."); $showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
} }
} }
} }

View File

@ -56,11 +56,11 @@ class Heading extends Component
return; return;
} }
if (data_get($this->application, 'settings.is_build_server_enabled') && str($this->application->docker_registry_image_name)->isEmpty()) { if (data_get($this->application, 'settings.is_build_server_enabled') && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'To use a build server, you must first set a Docker image.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>'); $this->dispatch('error', 'Failed to deploy.', 'To use a build server, you must first set a Docker image.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/build-server">documentation</a>');
return; return;
} }
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) { if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>'); $this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
return; return;
} }
$this->setDeploymentUuid(); $this->setDeploymentUuid();
@ -99,7 +99,7 @@ class Heading extends Component
public function restart() public function restart()
{ {
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) { if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>'); $this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
return; return;
} }
$this->setDeploymentUuid(); $this->setDeploymentUuid();

View File

@ -42,7 +42,7 @@ class BackupEdit extends Component
public function delete() public function delete()
{ {
// TODO: Delete backup from server and add a confirmation modal try {
$this->backup->delete(); $this->backup->delete();
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') { if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
$previousUrl = url()->previous(); $previousUrl = url()->previous();
@ -54,7 +54,9 @@ class BackupEdit extends Component
} else { } else {
return redirect()->route('project.database.backup.index', $this->parameters); return redirect()->route('project.database.backup.index', $this->parameters);
} }
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function instantSave() public function instantSave()
@ -63,7 +65,7 @@ class BackupEdit extends Component
$this->custom_validate(); $this->custom_validate();
$this->backup->save(); $this->backup->save();
$this->backup->refresh(); $this->backup->refresh();
$this->dispatch('success', 'Backup updated successfully'); $this->dispatch('success', 'Backup updated successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage()); $this->dispatch('error', $e->getMessage());
} }

View File

@ -46,10 +46,11 @@ class Edit extends Component
public function submit() public function submit()
{ {
$this->validate();
try { try {
$this->validate();
$this->project->save(); $this->project->save();
$this->dispatch('saved'); $this->dispatch('saved');
$this->dispatch('success', 'Project updated.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@ -2,6 +2,7 @@
namespace App\Livewire\Project; namespace App\Livewire\Project;
use App\Models\PrivateKey;
use App\Models\Project; use App\Models\Project;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@ -10,7 +11,9 @@ class Index extends Component
{ {
public $projects; public $projects;
public $servers; public $servers;
public $private_keys;
public function mount() { public function mount() {
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get(); $this->projects = Project::ownedByCurrentTeam()->get();
$this->servers = Server::ownedByCurrentTeam()->count(); $this->servers = Server::ownedByCurrentTeam()->count();
} }

View File

@ -49,7 +49,6 @@ class Select extends Component
} }
public function render() public function render()
{ {
$this->loadServices();
return view('livewire.project.new.select'); return view('livewire.project.new.select');
} }
@ -74,6 +73,7 @@ class Select extends Component
public function loadServices(bool $force = false) public function loadServices(bool $force = false)
{ {
try { try {
$this->loadingServices = true;
if (count($this->allServices) > 0 && !$force) { if (count($this->allServices) > 0 && !$force) {
if (!$this->search) { if (!$this->search) {
$this->services = $this->allServices; $this->services = $this->allServices;
@ -90,8 +90,7 @@ class Select extends Component
$this->allServices = getServiceTemplates(); $this->allServices = getServiceTemplates();
$this->services = $this->allServices->filter(function ($service, $key) { $this->services = $this->allServices->filter(function ($service, $key) {
return str_contains(strtolower($key), strtolower($this->search)); return str_contains(strtolower($key), strtolower($this->search));
});; });
$this->dispatch('success', 'Successfully loaded services.');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@ -36,6 +36,30 @@ class Configuration extends Component
$this->applications = $this->service->applications->sort(); $this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort(); $this->databases = $this->service->databases->sort();
} }
public function restartApplication($id)
{
try {
$application = $this->service->applications->find($id);
if ($application) {
$application->restart();
$this->dispatch('success', 'Application restarted successfully.');
}
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function restartDatabase($id)
{
try {
$database = $this->service->databases->find($id);
if ($database) {
$database->restart();
$this->dispatch('success', 'Database restarted successfully.');
}
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function check_status() public function check_status()
{ {
try { try {

View File

@ -34,7 +34,11 @@ class Database extends Component
} }
$this->refreshFileStorages(); $this->refreshFileStorages();
} }
public function instantSaveAdvanced() public function instantSaveExclude()
{
$this->submit();
}
public function instantSaveLogDrain()
{ {
if (!$this->database->service->destination->server->isLogDrainEnabled()) { if (!$this->database->service->destination->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false; $this->database->is_log_drain_enabled = false;

View File

@ -1,11 +1,11 @@
<?php <?php
namespace App\Livewire\Modal; namespace App\Livewire\Project\Service;
use App\Models\Service; use App\Models\Service;
use LivewireUI\Modal\ModalComponent; use Livewire\Component;
class EditCompose extends ModalComponent class EditCompose extends Component
{ {
public Service $service; public Service $service;
public $serviceId; public $serviceId;
@ -16,13 +16,13 @@ class EditCompose extends ModalComponent
public function mount() { public function mount() {
$this->service = Service::find($this->serviceId); $this->service = Service::find($this->serviceId);
} }
public function render()
{ public function saveEditedCompose() {
return view('livewire.modal.edit-compose');
}
public function submit() {
$this->dispatch('warning', "Saving new docker compose..."); $this->dispatch('warning', "Saving new docker compose...");
$this->dispatch('saveCompose', $this->service->docker_compose_raw); $this->dispatch('saveCompose', $this->service->docker_compose_raw);
$this->closeModal(); }
public function render()
{
return view('livewire.project.service.edit-compose');
} }
} }

View File

@ -1,13 +0,0 @@
<?php
namespace App\Livewire\Project\Service;
use Livewire\Component;
class Modal extends Component
{
public function render()
{
return view('livewire.project.service.modal');
}
}

View File

@ -16,17 +16,24 @@ class Navbar extends Component
public array $parameters; public array $parameters;
public array $query; public array $query;
public $isDeploymentProgress = false; public $isDeploymentProgress = false;
public function getListeners() public function getListeners()
{ {
$userId = auth()->user()->id;
return [ return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted',
"serviceStatusChanged" "serviceStatusChanged"
]; ];
} }
public function serviceStarted() {
$this->dispatch('success', 'Service status changed.');
}
public function serviceStatusChanged() public function serviceStatusChanged()
{ {
$this->dispatch('refresh')->self(); $this->dispatch('refresh')->self();
} }
public function check_status() { public function check_status()
{
$this->dispatch('check_status'); $this->dispatch('check_status');
$this->dispatch('success', 'Service status updated.'); $this->dispatch('success', 'Service status updated.');
} }
@ -44,7 +51,7 @@ class Navbar extends Component
$this->isDeploymentProgress = false; $this->isDeploymentProgress = false;
} }
} }
public function deploy() public function start()
{ {
$this->checkDeployments(); $this->checkDeployments();
if ($this->isDeploymentProgress) { if ($this->isDeploymentProgress) {
@ -73,9 +80,9 @@ class Navbar extends Component
return; return;
} }
PullImage::run($this->service); PullImage::run($this->service);
$this->dispatch('image-pulled');
StopService::run($this->service); StopService::run($this->service);
$this->service->parse(); $this->service->parse();
$this->dispatch('imagePulled');
$activity = StartService::run($this->service); $activity = StartService::run($this->service);
$this->dispatch('activityMonitor', $activity->id); $this->dispatch('activityMonitor', $activity->id);
} }

View File

@ -59,7 +59,7 @@ class ServiceApplicationView extends Component
$this->validate(); $this->validate();
$this->application->save(); $this->application->save();
updateCompose($this->application); updateCompose($this->application);
$this->dispatch('success', 'Application saved.'); $this->dispatch('success', 'Service saved.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} finally { } finally {

View File

@ -2,11 +2,12 @@
namespace App\Livewire\Project\Service; namespace App\Livewire\Project\Service;
use App\Models\Service;
use Livewire\Component; use Livewire\Component;
class StackForm extends Component class StackForm extends Component
{ {
public $service; public Service $service;
public $fields = []; public $fields = [];
protected $listeners = ["saveCompose"]; protected $listeners = ["saveCompose"];
public $rules = [ public $rules = [

View File

@ -56,7 +56,7 @@ class Destination extends Component
public function redeploy(int $network_id, int $server_id) public function redeploy(int $network_id, int $server_id)
{ {
if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) { if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>'); $this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
return; return;
} }
$deployment_uuid = new Cuid2(7); $deployment_uuid = new Cuid2(7);

View File

@ -98,6 +98,7 @@ class All extends Component
} }
} }
$environment->is_build_time = false; $environment->is_build_time = false;
$environment->is_multiline = false;
$environment->is_preview = $isPreview ? true : false; $environment->is_preview = $isPreview ? true : false;
switch ($this->resource->type()) { switch ($this->resource->type()) {
case 'application': case 'application':

View File

@ -33,6 +33,7 @@ class Add extends Component
public function submit() public function submit()
{ {
try {
$this->validate(); $this->validate();
$isValid = validate_cron_expression($this->frequency); $isValid = validate_cron_expression($this->frequency);
if (!$isValid) { if (!$isValid) {
@ -46,6 +47,9 @@ class Add extends Component
'container' => $this->container, 'container' => $this->container,
]); ]);
$this->clear(); $this->clear();
} catch (\Exception $e) {
return handleError($e, $this);
}
} }
public function clear() public function clear()

View File

@ -28,7 +28,7 @@ class Tags extends Component
{ {
try { try {
if ($this->resource->tags()->where('id', $id)->exists()) { if ($this->resource->tags()->where('id', $id)->exists()) {
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='text-warning'>$name</span> already added."); $this->dispatch('error', 'Duplicate tags.', "Tag <span class='dark:text-warning'>$name</span> already added.");
return; return;
} }
$this->resource->tags()->syncWithoutDetaching($id); $this->resource->tags()->syncWithoutDetaching($id);
@ -66,7 +66,7 @@ class Tags extends Component
$tags = str($this->new_tag)->trim()->explode(' '); $tags = str($this->new_tag)->trim()->explode(' ');
foreach ($tags as $tag) { foreach ($tags as $tag) {
if ($this->resource->tags()->where('name', $tag)->exists()) { if ($this->resource->tags()->where('name', $tag)->exists()) {
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='text-warning'>$tag</span> already added."); $this->dispatch('error', 'Duplicate tags.', "Tag <span class='dark:text-warning'>$tag</span> already added.");
continue; continue;
} }
$found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first(); $found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first();

View File

@ -1,25 +0,0 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class RealtimeConnection extends Component
{
public $checkConnection = false;
public $showNotification = false;
public $isNotificationEnabled = true;
public function render()
{
return view('livewire.realtime-connection');
}
public function disable()
{
auth()->user()->update(['is_notification_realtime_enabled' => false]);
$this->showNotification = false;
}
public function mount() {
$this->isNotificationEnabled = auth()->user()->is_notification_realtime_enabled;
$this->checkConnection = auth()->user()->id === 0;
}
}

View File

@ -67,7 +67,7 @@ class Create extends Component
'team_id' => currentTeam()->id 'team_id' => currentTeam()->id
]); ]);
if ($this->from === 'server') { if ($this->from === 'server') {
return redirect()->route('server.create'); return redirect()->route('dashboard');
} }
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]); return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@ -50,6 +50,7 @@ class Show extends Component
$this->private_key->private_key = formatPrivateKey($this->private_key->private_key); $this->private_key->private_key = formatPrivateKey($this->private_key->private_key);
$this->private_key->save(); $this->private_key->save();
refresh_server_connection($this->private_key); refresh_server_connection($this->private_key);
$this->dispatch('success', 'Private key updated.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@ -0,0 +1,45 @@
<?php
namespace App\Livewire\Server;
use App\Actions\Server\ConfigureCloudflared;
use App\Models\Server;
use Livewire\Component;
class ConfigureCloudflareTunnels extends Component
{
public $server_id;
public string $cloudflare_token;
public string $ssh_domain;
public function alreadyConfigured()
{
try {
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
$server->settings->is_cloudflare_tunnel = true;
$server->settings->save();
$this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
$this->dispatch('serverInstalled');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit()
{
try {
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
ConfigureCloudflared::run($server, $this->cloudflare_token);
$server->settings->is_cloudflare_tunnel = true;
$server->ip = $this->ssh_domain;
$server->save();
$server->settings->save();
$this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
$this->dispatch('serverInstalled');
} catch(\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.configure-cloudflare-tunnels');
}
}

View File

@ -82,7 +82,7 @@ class Form extends Component
$this->server->settings->is_usable = true; $this->server->settings->is_usable = true;
$this->server->settings->save(); $this->server->settings->save();
} else { } else {
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.'); $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.');
return; return;
} }
} }

View File

@ -53,7 +53,7 @@ class ByIp extends Component
public function mount() public function mount()
{ {
$this->name = generate_random_name(); $this->name = generate_random_name();
$this->private_key_id = $this->private_keys->first()->id; $this->private_key_id = $this->private_keys->first()?->id;
$this->swarm_managers = Server::isUsable()->get()->where('settings.is_swarm_manager', true); $this->swarm_managers = Server::isUsable()->get()->where('settings.is_swarm_manager', true);
if ($this->swarm_managers->count() > 0) { if ($this->swarm_managers->count() > 0) {
$this->selected_swarm_cluster = $this->swarm_managers->first()->id; $this->selected_swarm_cluster = $this->swarm_managers->first()->id;

View File

@ -49,7 +49,8 @@ class Deploy extends Component
{ {
$this->server->refresh(); $this->server->refresh();
} }
public function restart() { public function restart()
{
try { try {
$this->stop(); $this->stop();
$this->dispatch('checkProxy'); $this->dispatch('checkProxy');

View File

@ -39,7 +39,7 @@ class ShowPrivateKey extends Component
if ($uptime) { if ($uptime) {
$this->dispatch('success', 'Server is reachable.'); $this->dispatch('success', 'Server is reachable.');
} else { } 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/server/openssh#openssh">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/knowledge-base/server/openssh">documentation</a> for further help.');
return; return;
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@ -75,7 +75,7 @@ class ValidateAndInstall extends Component
{ {
$this->uptime = $this->server->validateConnection(); $this->uptime = $this->server->validateConnection();
if (!$this->uptime) { if (!$this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.'; $this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.';
return; return;
} }
$this->dispatch('validateOS'); $this->dispatch('validateOS');

View File

@ -0,0 +1,43 @@
<?php
namespace App\Livewire\Settings;
use Livewire\Component;
use App\Models\OauthSetting;
class Auth extends Component {
public $oauth_settings_map;
protected function rules() {
return OauthSetting::all()->reduce(function($carry, $setting) {
$carry["oauth_settings_map.$setting->provider.enabled"] = 'required';
$carry["oauth_settings_map.$setting->provider.client_id"] = 'nullable';
$carry["oauth_settings_map.$setting->provider.client_secret"] = 'nullable';
$carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable';
$carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable';
return $carry;
}, []);
}
public function mount() {
$this->oauth_settings_map = OauthSetting::all()->sortBy('provider')->reduce(function($carry, $setting) {
$carry[$setting->provider] = $setting;
return $carry;
}, []);
}
private function updateOauthSettings() {
foreach (array_values($this->oauth_settings_map) as &$setting) {
$setting->save();
}
}
public function instantSave() {
$this->updateOauthSettings();
}
public function submit() {
$this->updateOauthSettings();
$this->dispatch('success', 'Instance settings updated successfully!');
}
}

View File

@ -58,6 +58,8 @@ class Email extends Component
try { try {
$this->resetErrorBag(); $this->resetErrorBag();
$this->validate([ $this->validate([
'settings.smtp_from_address' => 'required|email',
'settings.smtp_from_name' => 'required',
'settings.resend_api_key' => 'required' 'settings.resend_api_key' => 'required'
]); ]);
$this->settings->save(); $this->settings->save();
@ -90,6 +92,8 @@ class Email extends Component
try { try {
$this->resetErrorBag(); $this->resetErrorBag();
$this->validate([ $this->validate([
'settings.smtp_from_address' => 'required|email',
'settings.smtp_from_name' => 'required',
'settings.smtp_host' => 'required', 'settings.smtp_host' => 'required',
'settings.smtp_port' => 'required|numeric', 'settings.smtp_port' => 'required|numeric',
'settings.smtp_encryption' => 'nullable', 'settings.smtp_encryption' => 'nullable',

View File

@ -30,7 +30,7 @@ class License extends Component
} }
public function render() public function render()
{ {
return view('livewire.settings.license')->layout('layouts.subscription'); return view('livewire.settings.license');
} }
public function submit() public function submit()
{ {

View File

@ -31,6 +31,6 @@ class Index extends Component
} }
public function render() public function render()
{ {
return view('livewire.subscription.index')->layout('layouts.subscription'); return view('livewire.subscription.index');
} }
} }

View File

@ -8,7 +8,9 @@ use Livewire\Component;
class SwitchTeam extends Component class SwitchTeam extends Component
{ {
public string $selectedTeamId = 'default'; public string $selectedTeamId = 'default';
public function mount() {
$this->selectedTeamId = auth()->user()->currentTeam()->id;
}
public function updatedSelectedTeamId() public function updatedSelectedTeamId()
{ {
$this->switch_to($this->selectedTeamId); $this->switch_to($this->selectedTeamId);

View File

@ -2,14 +2,73 @@
namespace App\Livewire\Tags; namespace App\Livewire\Tags;
use App\Http\Controllers\Api\Deploy;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Tag; use App\Models\Tag;
use Illuminate\Support\Collection;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
class Index extends Component class Index extends Component
{ {
public $tags = []; #[Url()]
public function mount() { public ?string $tag = null;
$this->tags = Tag::where('team_id', currentTeam()->id)->get()->unique('name')->sortBy('name');
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() public function render()
{ {

View File

@ -1,13 +0,0 @@
<?php
namespace App\Livewire\Team\Notification;
use Livewire\Component;
class Index extends Component
{
public function render()
{
return view('livewire.team.notification.index');
}
}

View File

@ -37,8 +37,8 @@ class Upgrade extends Component
return; return;
} }
$this->showProgress = true; $this->showProgress = true;
UpdateCoolify::run(true); UpdateCoolify::run(force: true, async: true);
$this->dispatch('success', "Upgrading to {$this->latestVersion} version..."); $this->dispatch('success', "Updating Coolify to {$this->latestVersion} version...");
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;
class OauthSetting extends Model
{
use HasFactory;
protected function clientSecret(): Attribute
{
return Attribute::make(
get: fn (string | null $value) => empty($value) ? null : Crypt::decryptString($value),
set: fn (string | null $value) => empty($value) ? null : Crypt::encryptString($value),
);
}
}

View File

@ -19,6 +19,11 @@ class ServiceApplication extends BaseModel
$service->fileStorages()->delete(); $service->fileStorages()->delete();
}); });
} }
public function restart()
{
$container_id = $this->name . '-' . $this->service->uuid;
instant_remote_process(["docker restart {$container_id}"], $this->service->server);
}
public function isLogDrainEnabled() public function isLogDrainEnabled()
{ {
return data_get($this, 'is_log_drain_enabled', false); return data_get($this, 'is_log_drain_enabled', false);

View File

@ -17,6 +17,11 @@ class ServiceDatabase extends BaseModel
$service->fileStorages()->delete(); $service->fileStorages()->delete();
}); });
} }
public function restart()
{
$container_id = $this->name . '-' . $this->service->uuid;
remote_process(["docker restart {$container_id}"], $this->service->server);
}
public function isLogDrainEnabled() public function isLogDrainEnabled()
{ {
return data_get($this, 'is_log_drain_enabled', false); return data_get($this, 'is_log_drain_enabled', false);
@ -52,8 +57,7 @@ class ServiceDatabase extends BaseModel
if ($this->service->server->isLocalhost() || isDev()) { if ($this->service->server->isLocalhost() || isDev()) {
$realIp = base_ip(); $realIp = base_ip();
} }
$url = "{$realIp}:{$port}"; return "{$realIp}:{$port}";
return $url;
} }
public function service() public function service()
{ {

View File

@ -177,9 +177,6 @@ class Team extends Model implements SendsDiscord, SendsEmail
if (isCloud()) { if (isCloud()) {
return true; return true;
} }
if (!data_get(auth()->user(), 'is_notification_notifications_enabled')) {
return true;
}
if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->use_instance_email_settings) { if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->use_instance_email_settings) {
return true; return true;
} }

View File

@ -53,13 +53,13 @@ class HighDiskUsage extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/automated-cleanup."; $message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.";
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
return [ return [
"message" => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/automated-cleanup." "message" => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup."
]; ];
} }
} }

View File

@ -18,6 +18,9 @@ class EventServiceProvider extends ServiceProvider
], ],
MaintenanceModeDisabled::class => [ MaintenanceModeDisabled::class => [
MaintenanceModeDisabledNotification::class, MaintenanceModeDisabledNotification::class,
],
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle',
], ],
ProxyStarted::class => [ ProxyStarted::class => [
ProxyStartedNotification::class, ProxyStartedNotification::class,

View File

@ -7,6 +7,7 @@ use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword; use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation; use App\Actions\Fortify\UpdateUserProfileInformation;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\OauthSetting;
use App\Models\User; use App\Models\User;
use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -56,13 +57,15 @@ class FortifyServiceProvider extends ServiceProvider
Fortify::loginView(function () { Fortify::loginView(function () {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
$enabled_oauth_providers = OauthSetting::where('enabled', true)->get();
$users = User::count(); $users = User::count();
if ($users == 0) { if ($users == 0) {
// If there are no users, redirect to registration // If there are no users, redirect to registration
return redirect()->route('register'); return redirect()->route('register');
} }
return view('auth.login', [ return view('auth.login', [
'is_registration_enabled' => $settings->is_registration_enabled 'is_registration_enabled' => $settings->is_registration_enabled,
'enabled_oauth_providers' => $enabled_oauth_providers,
]); ]);
}); });

View File

@ -13,10 +13,9 @@ class Button extends Component
*/ */
public function __construct( public function __construct(
public bool $disabled = false, public bool $disabled = false,
public bool $isModal = false,
public bool $noStyle = false, public bool $noStyle = false,
public ?string $modalId = null, 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) { if ($this->noStyle) {
$this->defaultClass = ""; $this->defaultClass = "";

View File

@ -12,14 +12,14 @@ class Checkbox extends Component
* Create a new component instance. * Create a new component instance.
*/ */
public function __construct( public function __construct(
public string|null $id = null, public ?string $id = null,
public string|null $name = null, public ?string $name = null,
public string|null $value = null, public ?string $value = null,
public string|null $label = null, public ?string $label = null,
public string|null $helper = null, public ?string $helper = null,
public string|bool $instantSave = false, public string|bool $instantSave = false,
public bool $disabled = 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 = "dark:border-neutral-700 text-coolgray-400 focus:ring-warning dark:bg-coolgray-100 rounded cursor-pointer dark:disabled:bg-base dark:disabled:cursor-not-allowed",
) { ) {
// //
} }

View File

@ -21,7 +21,7 @@ class Input extends Component
public ?string $helper = null, public ?string $helper = null,
public bool $allowToPeak = true, public bool $allowToPeak = true,
public bool $isMultiline = false, 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",
) { ) {
} }
@ -29,7 +29,9 @@ class Input extends Component
{ {
if (is_null($this->id)) $this->id = new Cuid2(7); if (is_null($this->id)) $this->id = new Cuid2(7);
if (is_null($this->name)) $this->name = $this->id; if (is_null($this->name)) $this->name = $this->id;
if ($this->type === 'password') {
$this->defaultClass = $this->defaultClass . " pr-[2.8rem]";
}
// $this->label = Str::title($this->label); // $this->label = Str::title($this->label);
return view('components.forms.input'); return view('components.forms.input');
} }

View File

@ -14,12 +14,12 @@ class Select extends Component
* Create a new component instance. * Create a new component instance.
*/ */
public function __construct( public function __construct(
public string|null $id = null, public ?string $id = null,
public string|null $name = null, public ?string $name = null,
public string|null $label = null, public ?string $label = null,
public string|null $helper = null, public ?string $helper = null,
public bool $required = false, 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 $defaultClass = "select"
) { ) {
// //
} }

View File

@ -25,8 +25,8 @@ class Textarea extends Component
public ?string $helper = null, public ?string $helper = null,
public bool $realtimeValidation = false, public bool $realtimeValidation = false,
public bool $allowToPeak = true, 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 $defaultClass = "input scrollbar",
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 $defaultClassInput = "input"
) { ) {
// //
} }

View File

@ -7,5 +7,5 @@ function get_team_id_from_token()
} }
function invalid_token() function invalid_token()
{ {
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400); return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400);
} }

View File

@ -27,7 +27,8 @@ const DATABASE_DOCKER_IMAGES = [
'couchdb', 'couchdb',
'neo4j', 'neo4j',
'influxdb', 'influxdb',
'clickhouse/clickhouse-server' 'clickhouse/clickhouse-server',
'supabase/postgres'
]; ];
const SPECIFIC_SERVICES = [ const SPECIFIC_SERVICES = [
'quay.io/minio/minio', 'quay.io/minio/minio',

View File

@ -94,7 +94,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$resource->service->docker_compose_raw = $dockerComposeRaw; $resource->service->docker_compose_raw = $dockerComposeRaw;
$resource->service->save(); $resource->service->save();
if (!str($resource->fqdn)->contains(',')) { if ($resource->fqdn && !str($resource->fqdn)->contains(',')) {
// Update FQDN // Update FQDN
$variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper(); $variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper();
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();

View File

@ -110,7 +110,7 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
ray($error); ray($error);
if ($error instanceof TooManyRequestsException) { if ($error instanceof TooManyRequestsException) {
if (isset($livewire)) { if (isset($livewire)) {
return $livewire->dispatch('error', 'Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.'); return $livewire->dispatch('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.");
} }
return "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds."; return "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.";
} }
@ -280,6 +280,10 @@ function base_url(bool $withPort = true): string
return url('/'); return url('/');
} }
function isSubscribed()
{
return auth()->user()->currentTeam()->subscription()->exists() || auth()->user()->isInstanceAdmin();
}
function isDev(): bool function isDev(): bool
{ {
return config('app.env') === 'local'; return config('app.env') === 'local';
@ -429,7 +433,7 @@ function sslip(Server $server)
function getServiceTemplates() function getServiceTemplates()
{ {
if (isDev()) { if (!isDev()) {
$services = File::get(base_path('templates/service-templates.json')); $services = File::get(base_path('templates/service-templates.json'));
$services = collect(json_decode($services))->sortKeys(); $services = collect(json_decode($services))->sortKeys();
} else { } else {
@ -444,13 +448,6 @@ function getServiceTemplates()
$services = collect([]); $services = collect([]);
} }
} }
// $version = config('version');
// $services = $services->map(function ($service) use ($version) {
// if (version_compare($version, data_get($service, 'minVersion', '0.0.0'), '<')) {
// $service->disabled = true;
// }
// return $service;
// });
return $services; return $services;
} }
@ -947,11 +944,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if (!$isDatabase) { if (!$isDatabase) {
if ($savedService->fqdn) { if ($savedService->fqdn) {
$fqdn = $savedService->fqdn . ',' . $fqdn; data_set($savedService, 'fqdn', $savedService->fqdn . ',' . $fqdn);
} else { } else {
$fqdn = $fqdn; data_set($savedService, 'fqdn', $fqdn);
} }
$savedService->fqdn = $fqdn;
$savedService->save(); $savedService->save();
} }
EnvironmentVariable::create([ EnvironmentVariable::create([
@ -963,7 +959,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
]); ]);
} }
// Caddy needs exact port in some cases. // Caddy needs exact port in some cases.
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) { if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) {
if ($resource->server->proxyType() === 'CADDY') { if ($resource->server->proxyType() === 'CADDY') {
$env = EnvironmentVariable::where([ $env = EnvironmentVariable::where([
@ -1001,6 +996,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'service_id' => $resource->id, 'service_id' => $resource->id,
])->first(); ])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
if (!is_null($command)) {
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') { if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) { if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($resource->server, $containerName); $fqdn = generateFqdn($resource->server, $containerName);
@ -1058,6 +1054,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
]); ]);
} }
} }
}
} else { } else {
if ($value->contains(':-')) { if ($value->contains(':-')) {
$key = $value->before(':'); $key = $value->before(':');
@ -1240,39 +1237,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
$baseName = generateApplicationContainerName($resource, $pull_request_id); $baseName = generateApplicationContainerName($resource, $pull_request_id);
$containerName = "$serviceName-$baseName"; $containerName = "$serviceName-$baseName";
if ($pull_request_id !== 0) {
if (count($serviceVolumes) > 0) { if (count($serviceVolumes) > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $pull_request_id, $topLevelVolumes) { $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(':');
$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,
]);
}
}
}
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)) { if (is_string($volume)) {
$volume = str($volume); $volume = str($volume);
if ($volume->contains(':') && !$volume->startsWith('/')) { if ($volume->contains(':') && !$volume->startsWith('/')) {
@ -1286,38 +1252,79 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($name->startsWith('~')) { if ($name->startsWith('~')) {
$name = $name->replaceFirst('~', $dir); $name = $name->replaceFirst('~', $dir);
} }
if ($pull_request_id !== 0) {
$name = $name . "-pr-$pull_request_id";
}
$volume = str("$name:$mount"); $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 { } else {
$topLevelVolumes->put($name->value(), [ $topLevelVolumes->put($name->value(), [
'name' => $name->value(), 'name' => $name->value(),
]); ]);
} }
} }
} 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)) { } else if (is_array($volume)) {
$source = data_get($volume, 'source'); $source = data_get($volume, 'source');
if ($source) { $target = data_get($volume, 'target');
if ((str($source)->startsWith('.') || str($source)->startsWith('~')) && !str($source)->startsWith('/')) { $read_only = data_get($volume, 'read_only');
if ($source && $target) {
if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) {
$dir = base_configuration_dir() . '/applications/' . $resource->uuid; $dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if (str($source, '.')) { if (str($source, '.')) {
$source = str('.', $dir, $source); $source = str($source)->replaceFirst('.', $dir);
} }
if (str($source, '~')) { if (str($source, '~')) {
$source = str('~', $dir, $source); $source = str($source)->replaceFirst('~', $dir);
} }
data_set($volume, 'source', $source); if ($pull_request_id !== 0) {
$source = $source . "-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source . ':' . $target . ':ro');
} else { } 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, [ $topLevelVolumes->put($source, [
'name' => $source, 'name' => $source,
]); ]);
} }
} }
} }
}
if (is_array($volume)) {
return data_get($volume, 'source');
}
return $volume->value(); return $volume->value();
}); });
data_set($service, 'volumes', $serviceVolumes->toArray()); data_set($service, 'volumes', $serviceVolumes->toArray());
} }
}
// Decide if the service is a database // Decide if the service is a database
$isDatabase = isDatabaseImage(data_get_str($service, 'image')); $isDatabase = isDatabaseImage(data_get_str($service, 'image'));
data_set($service, 'is_database', $isDatabase); data_set($service, 'is_database', $isDatabase);
@ -1450,13 +1457,14 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
])->first(); ])->first();
$value = Str::of(replaceVariables($value)); $value = Str::of(replaceVariables($value));
$key = $value; $key = $value;
if ($value->startsWith('SERVICE_')) { if ($value->startsWith('SERVICE_')) {
$foundEnv = EnvironmentVariable::where([ $foundEnv = EnvironmentVariable::where([
'key' => $key, 'key' => $key,
'application_id' => $resource->id, 'application_id' => $resource->id,
])->first(); ])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
ray($command, $generatedValue);
if (!is_null($command)) {
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') { if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) { if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($server, $containerName); $fqdn = generateFqdn($server, $containerName);
@ -1469,7 +1477,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($foundEnv) { if ($foundEnv) {
$fqdn = data_get($foundEnv, 'value'); $fqdn = data_get($foundEnv, 'value');
} else { } else {
if ($command->value() === 'URL') { if ($command?->value() === 'URL') {
$fqdn = Str::of($fqdn)->after('://')->value(); $fqdn = Str::of($fqdn)->after('://')->value();
} }
EnvironmentVariable::create([ EnvironmentVariable::create([
@ -1492,6 +1500,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
]); ]);
} }
} }
}
} else { } else {
if ($value->contains(':-')) { if ($value->contains(':-')) {
$key = $value->before(':'); $key = $value->before(':');
@ -1602,6 +1611,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $service; 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 = [ $finalServices = [
'version' => $dockerComposeVersion, 'version' => $dockerComposeVersion,
'services' => $services->toArray(), 'services' => $services->toArray(),
@ -1635,7 +1650,7 @@ function parseEnvVariable(Str|string $value)
$forService = null; $forService = null;
$generatedValue = null; $generatedValue = null;
$port = null; $port = null;
if ($value->startsWith('SERVICE')) {
if ($count === 2) { if ($count === 2) {
if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) { if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) {
// SERVICE_FQDN_UMAMI // SERVICE_FQDN_UMAMI
@ -1660,6 +1675,7 @@ function parseEnvVariable(Str|string $value)
$command = $value->after('SERVICE_')->beforeLast('_'); $command = $value->after('SERVICE_')->beforeLast('_');
} }
} }
}
return [ return [
'command' => $command, 'command' => $command,
'forService' => $forService, 'forService' => $forService,

View File

@ -0,0 +1,37 @@
<?php
use App\Models\OauthSetting;
use Laravel\Socialite\Facades\Socialite;
function get_socialite_provider(string $provider)
{
$oauth_setting = OauthSetting::firstWhere('provider', $provider);
if ($provider == 'azure') {
$azure_config = new \SocialiteProviders\Manager\Config(
$oauth_setting->client_id,
$oauth_setting->client_secret,
$oauth_setting->redirect_uri,
['tenant' => $oauth_setting->tenant],
);
return Socialite::driver('azure')->setConfig($azure_config);
}
$config = [
'client_id' => $oauth_setting->client_id,
'client_secret' => $oauth_setting->client_secret,
'redirect' => $oauth_setting->redirect_uri,
];
$provider_class_map = [
'bitbucket' => \Laravel\Socialite\Two\BitbucketProvider::class,
'github' => \Laravel\Socialite\Two\GithubProvider::class,
'gitlab' => \Laravel\Socialite\Two\GitlabProvider::class,
'google' => \Laravel\Socialite\Two\GoogleProvider::class,
];
return Socialite::buildProvider(
$provider_class_map[$provider],
$config
);
}

View File

@ -17,6 +17,7 @@
"laravel/horizon": "^5.15", "laravel/horizon": "^5.15",
"laravel/prompts": "^0.1.6", "laravel/prompts": "^0.1.6",
"laravel/sanctum": "^v3.2.1", "laravel/sanctum": "^v3.2.1",
"laravel/socialite": "^5.12",
"laravel/tinker": "^v2.8.1", "laravel/tinker": "^v2.8.1",
"laravel/ui": "^4.2", "laravel/ui": "^4.2",
"lcobucci/jwt": "^5.0.0", "lcobucci/jwt": "^5.0.0",
@ -31,6 +32,7 @@
"pusher/pusher-php-server": "^7.2", "pusher/pusher-php-server": "^7.2",
"resend/resend-laravel": "^0.5.0", "resend/resend-laravel": "^0.5.0",
"sentry/sentry-laravel": "^3.4", "sentry/sentry-laravel": "^3.4",
"socialiteproviders/microsoft-azure": "^5.1",
"spatie/laravel-activitylog": "^4.7.3", "spatie/laravel-activitylog": "^4.7.3",
"spatie/laravel-data": "^3.4.3", "spatie/laravel-data": "^3.4.3",
"spatie/laravel-ray": "^1.32.4", "spatie/laravel-ray": "^1.32.4",
@ -39,7 +41,6 @@
"stripe/stripe-php": "^12.0", "stripe/stripe-php": "^12.0",
"symfony/yaml": "^6.2", "symfony/yaml": "^6.2",
"visus/cuid2": "^2.0.0", "visus/cuid2": "^2.0.0",
"wire-elements/modal": "^2.0",
"yosymfony/toml": "^1.0" "yosymfony/toml": "^1.0"
}, },
"require-dev": { "require-dev": {

829
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -187,6 +187,7 @@ return [
/* /*
* Package Service Providers... * Package Service Providers...
*/ */
\SocialiteProviders\Manager\ServiceProvider::class,
/* /*
* Application Service Providers... * Application Service Providers...

View File

@ -19,7 +19,9 @@ return [
], ],
], ],
'services' => [ 'services' => [
'official' => 'https://cdn.coollabs.io/coolify/service-templates.json', // Temporary disabled until cache is implemented
// 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json',
], ],
'limits' => [ 'limits' => [
'trial_period' => 0, 'trial_period' => 0,

View File

@ -12,4 +12,6 @@ return [
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false), 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'), '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),
]; ];

View File

@ -3,11 +3,11 @@
return [ return [
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/ // @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
'dsn' => 'https://f0b0e6be13926d4ac68d68d51d38db8f@o1082494.ingest.us.sentry.io/4505347448045568', 'dsn' => 'https://89552af6db48f4ca6a871ec0fc42964d@o1082494.ingest.us.sentry.io/4505347448045568',
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.241', 'release' => '4.0.0-beta.248',
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

View File

@ -31,4 +31,11 @@ return [
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
], ],
'azure' => [
'client_id' => env('AZURE_CLIENT_ID'),
'client_secret' => env('AZURE_CLIENT_SECRET'),
'redirect' => env('AZURE_REDIRECT_URI'),
'tenant' => env('AZURE_TENANT_ID'),
'proxy' => env('AZURE_PROXY'),
],
]; ];

View File

@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.241'; return '4.0.0-beta.248';

View File

@ -1,52 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Include CSS
|--------------------------------------------------------------------------
|
| The modal uses TailwindCSS, if you don't use TailwindCSS you will need
| to set this parameter to true. This includes the modern-normalize css.
|
*/
'include_css' => false,
/*
|--------------------------------------------------------------------------
| Include JS
|--------------------------------------------------------------------------
|
| Livewire UI will inject the required Javascript in your blade template.
| If you want to bundle the required Javascript you can set this to false
| and add `require('vendor/wire-elements/modal/resources/js/modal');`
| to your script bundler like webpack.
|
*/
'include_js' => false,
/*
|--------------------------------------------------------------------------
| Modal Component Defaults
|--------------------------------------------------------------------------
|
| Configure the default properties for a modal component.
|
| Supported modal_max_width
| 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl', '7xl'
*/
'component_defaults' => [
'modal_max_width' => '7xl',
'close_modal_on_click_away' => true,
'close_modal_on_escape' => true,
'close_modal_on_escape_is_forceful' => true,
'dispatch_close_event' => false,
'destroy_on_close' => false,
],
];

View File

@ -0,0 +1,28 @@
<?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::table('users', function (Blueprint $table) {
$table->string('password')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('password')->nullable(false)->change();
});
}
};

View File

@ -0,0 +1,33 @@
<?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('oauth_settings', function (Blueprint $table) {
$table->id();
$table->string('provider')->unique();
$table->boolean('enabled')->default(false);
$table->string('client_id')->nullable();
$table->text('client_secret')->nullable();
$table->string('redirect_uri')->nullable();
$table->string('tenant')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('oauth_settings');
}
};

Some files were not shown because too many files have changed in this diff Show More