Merge pull request #1179 from coollabsio/next

v4.0.0-beta.19
This commit is contained in:
Andras Bacsai 2023-08-15 15:15:58 +02:00 committed by GitHub
commit aba86dffd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
421 changed files with 9406 additions and 3391 deletions

View File

@ -1,15 +1,15 @@
name: Coolify Builder (v4)
name: Coolify Helper Image (v4)
on:
push:
branches: ["main", "v4"]
branches: [ "main", "next" ]
paths:
- .github/workflows/coolify-builder.yml
- docker/coolify-builder/Dockerfile
- .github/workflows/coolify-helper.yml
- docker/coolify-helper/Dockerfile
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify-builder"
IMAGE_NAME: "coollabsio/coolify-helper"
jobs:
amd64:
@ -30,12 +30,12 @@ jobs:
with:
no-cache: true
context: .
file: docker/coolify-builder/Dockerfile
file: docker/coolify-helper/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
aarch64:
runs-on: [self-hosted, arm64]
runs-on: [ self-hosted, arm64 ]
permissions:
contents: read
packages: write
@ -52,7 +52,7 @@ jobs:
with:
no-cache: true
context: .
file: docker/coolify-builder/Dockerfile
file: docker/coolify-helper/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64
@ -61,7 +61,7 @@ jobs:
permissions:
contents: read
packages: write
needs: [amd64, aarch64]
needs: [ amd64, aarch64 ]
steps:
- name: Checkout
uses: actions/checkout@v3

View File

@ -15,6 +15,7 @@ class PrepareCoolifyTask
{
protected Activity $activity;
protected CoolifyTaskArgs $remoteProcessArgs;
public function __construct(CoolifyTaskArgs $remoteProcessArgs)
{
$this->remoteProcessArgs = $remoteProcessArgs;

View File

@ -49,6 +49,28 @@ public function __construct(Activity $activity, bool $hide_from_output = false,
$this->ignore_errors = $ignore_errors;
}
public static function decodeOutput(?Activity $activity = null): string
{
if (is_null($activity)) {
return '';
}
try {
$decoded = json_decode(
data_get($activity, 'description'),
associative: true,
flags: JSON_THROW_ON_ERROR
);
} catch (\JsonException $exception) {
return '';
}
return collect($decoded)
->sortBy(fn ($i) => $i['order'])
->map(fn ($i) => $i['output'])
->implode("");
}
public function __invoke(): ProcessResult
{
$this->time_start = hrtime(true);
@ -83,15 +105,6 @@ public function __invoke(): ProcessResult
return $processResult;
}
protected function getLatestCounter(): int
{
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
if ($description === null || count($description) === 0) {
return 1;
}
return end($description)['order'] + 1;
}
protected function getCommand(): string
{
$user = $this->activity->getExtraProperty('user');
@ -120,6 +133,13 @@ protected function handleOutput(string $type, string $output)
}
}
protected function elapsedTime(): int
{
$timeMs = (hrtime(true) - $this->time_start) / 1_000_000;
return intval($timeMs);
}
public function encodeOutput($type, $output)
{
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
@ -135,26 +155,13 @@ public function encodeOutput($type, $output)
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR);
}
public static function decodeOutput(?Activity $activity = null): string
protected function getLatestCounter(): int
{
if (is_null($activity)) {
return '';
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
if ($description === null || count($description) === 0) {
return 1;
}
try {
$decoded = json_decode(
data_get($activity, 'description'),
associative: true,
flags: JSON_THROW_ON_ERROR
);
} catch (\JsonException $exception) {
return '';
}
return collect($decoded)
->sortBy(fn ($i) => $i['order'])
->map(fn ($i) => $i['output'])
->implode("");
return end($description)['order'] + 1;
}
/**
@ -171,11 +178,4 @@ protected function isAfterLastThrottle()
return ($this->current_time - $this->throttle_interval_ms) > $this->last_write_at;
}
protected function elapsedTime(): int
{
$timeMs = (hrtime(true) - $this->time_start) / 1_000_000;
return intval($timeMs);
}
}

View File

@ -0,0 +1,164 @@
<?php
namespace App\Actions\Database;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
class StartPostgresql
{
public StandalonePostgresql $database;
public array $commands = [];
public array $init_scripts = [];
public string $configuration_dir;
public function __invoke(Server $server, StandalonePostgresql $database)
{
$this->database = $database;
$container_name = generate_container_name($this->database->uuid);
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [
"mkdir -p $this->configuration_dir",
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
];
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->generate_init_scripts();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
'container_name' => $container_name,
'environment' => $environment_variables,
'restart' => 'always',
'networks' => [
$this->database->destination->network,
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'pg_isready',
'-d',
$this->database->postgres_db,
'-U',
$this->database->postgres_user,
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
'networks' => [
$this->database->destination->network => [
'external' => false,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
];
if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
}
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (count($this->init_scripts) > 0) {
foreach ($this->init_scripts as $init_script) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $init_script,
'target' => '/docker-entrypoint-initdb.d/' . basename($init_script),
'read_only' => true,
];
}
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
return remote_process($this->commands, $server);
}
private function generate_local_persistent_volumes()
{
$local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
}
return $local_persistent_volumes;
}
private function generate_local_persistent_volumes_only_volume_names()
{
$local_persistent_volumes_names = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path) {
continue;
}
$name = $persistentStorage->name;
$local_persistent_volumes_names[$name] = [
'name' => $name,
'external' => false,
];
}
return $local_persistent_volumes_names;
}
private function generate_environment_variables()
{
$environment_variables = collect();
ray('Generate Environment Variables')->green();
ray($this->database->runtime_environment_variables)->green();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
}
return $environment_variables->all();
}
private function generate_init_scripts()
{
if (is_null($this->database->init_scripts) || count($this->database->init_scripts) === 0) {
return;
}
foreach ($this->database->init_scripts as $init_script) {
$filename = data_get($init_script, 'filename');
$content = data_get($init_script, 'content');
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
}
}
}

View File

@ -3,11 +3,8 @@
namespace App\Actions\Fortify;
use App\Models\InstanceSettings;
use App\Models\Team;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\CreatesNewUsers;
@ -25,7 +22,6 @@ public function create(array $input): User
{
$settings = InstanceSettings::get();
if (!$settings->is_registration_enabled) {
Log::info('Registration is disabled');
abort(403);
}
Validator::make($input, [

View File

@ -29,8 +29,10 @@ public function update(User $user, array $input): void
],
])->validateWithBag('updateProfileInformation');
if ($input['email'] !== $user->email &&
$user instanceof MustVerifyEmail) {
if (
$input['email'] !== $user->email &&
$user instanceof MustVerifyEmail
) {
$this->updateVerifiedUser($user, $input);
} else {
$user->forceFill([

View File

@ -4,7 +4,6 @@
use App\Models\InstanceSettings;
use Illuminate\Support\Facades\Http;
use Visus\Cuid2\Cuid2;
class CheckResaleLicense
{
@ -12,41 +11,52 @@ public function __invoke()
{
try {
$settings = InstanceSettings::get();
$instance_id = config('app.id');
$settings->update([
'is_resale_license_active' => false,
]);
if (is_dev()) {
return;
}
if (!$settings->resale_license) {
return;
}
ray('Checking license key');
$base_url = config('coolify.license_url');
if (is_dev()) {
$base_url = 'http://host.docker.internal:8787';
}
$instance_id = config('app.id');
ray("Checking license key against $base_url/lemon/validate");
$data = Http::withHeaders([
'Accept' => 'application/json',
])->post('https://api.lemonsqueezy.com/v1/licenses/validate', [
])->get("$base_url/lemon/validate", [
'license_key' => $settings->resale_license,
'instance_name' => $instance_id,
])->throw()->json();
$product_id = (int)data_get($data, 'meta.product_id');
$valid_product_id = (int)config('coolify.lemon_squeezy_product_id');
if ($product_id !== $valid_product_id) {
throw new \Exception('Invalid product id');
}
ray('Valid Product Id');
['valid' => $valid, 'license_key' => $license_key] = $data;
if ($valid) {
if (data_get($license_key, 'status') === 'inactive') {
Http::withHeaders([
'Accept' => 'application/json',
])->post('https://api.lemonsqueezy.com/v1/licenses/activate', [
'license_key' => $settings->resale_license,
'instance_name' => $instance_id,
])->throw()->json();
}
'instance_id' => $instance_id,
])->json();
if (data_get($data, 'valid') === true && data_get($data, 'license_key.status') === 'active') {
ray('Valid & active license key');
$settings->update([
'is_resale_license_active' => true,
]);
return;
}
throw new \Exception('Invalid license key');
$data = Http::withHeaders([
'Accept' => 'application/json',
])->get("$base_url/lemon/activate", [
'license_key' => $settings->resale_license,
'instance_id' => $instance_id,
])->json();
if (data_get($data, 'activated') === true) {
ray('Activated license key');
$settings->update([
'is_resale_license_active' => true,
]);
return;
}
if (data_get($data, 'license_key.status') === 'active') {
throw new \Exception('Invalid license key.');
}
throw new \Exception('Cannot activate license key.');
} catch (\Throwable $th) {
ray($th);
$settings->update([

View File

@ -0,0 +1,25 @@
<?php
namespace App\Actions\Proxy;
use App\Models\Server;
use Illuminate\Support\Str;
class CheckConfigurationSync
{
public function __invoke(Server $server, bool $reset = false)
{
$proxy_path = get_proxy_path();
$proxy_configuration = instant_remote_process([
"cat $proxy_path/docker-compose.yml",
], $server, false);
if ($reset || is_null($proxy_configuration)) {
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
resolve(SaveConfigurationSync::class)($server, $proxy_configuration);
return $proxy_configuration;
}
return $proxy_configuration;
}
}

View File

@ -1,33 +0,0 @@
<?php
namespace App\Actions\Proxy;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Illuminate\Support\Str;
class CheckProxySettingsInSync
{
public function __invoke(Server $server, bool $reset = false)
{
$proxy_path = config('coolify.proxy_config_path');
$output = instant_remote_process([
"cat $proxy_path/docker-compose.yml",
], $server, false);
if (is_null($output) || $reset) {
$final_output = Str::of(getProxyConfiguration($server))->trim()->value;
} else {
$final_output = Str::of($output)->trim()->value;
}
$docker_compose_yml_base64 = base64_encode($final_output);
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
if (is_null($output) || $reset) {
instant_remote_process([
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server);
}
return $final_output;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Actions\Proxy;
use App\Models\Server;
use Illuminate\Support\Str;
class SaveConfigurationSync
{
public function __invoke(Server $server, string $configuration)
{
$proxy_path = get_proxy_path();
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
instant_remote_process([
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server);
}
}

View File

@ -5,8 +5,8 @@
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str;
use Spatie\Activitylog\Models\Activity;
class StartProxy
{
@ -18,8 +18,7 @@ public function __invoke(Server $server): Activity
$server->proxy->status = ProxyStatus::EXITED->value;
$server->save();
}
$proxy_path = config('coolify.proxy_config_path');
$proxy_path = get_proxy_path();
$networks = collect($server->standaloneDockers)->map(function ($docker) {
return $docker['network'];
})->unique();
@ -30,29 +29,24 @@ public function __invoke(Server $server): Activity
return "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null 2>&1 || docker network create --attachable $network > /dev/null 2>&1";
});
$configuration = instant_remote_process([
"cat $proxy_path/docker-compose.yml",
], $server, false);
if (is_null($configuration)) {
$configuration = Str::of(getProxyConfiguration($server))->trim()->value;
} else {
$configuration = Str::of($configuration)->trim()->value;
}
$configuration = resolve(CheckConfigurationSync::class)($server);
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
$activity = remote_process([
"echo 'Creating required Docker networks...'",
...$create_networks_command,
"mkdir -p $proxy_path",
"cd $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
"echo 'Creating Docker Compose file...'",
"echo 'Pulling docker image...'",
'docker compose pull -q',
"echo 'Stopping old proxy...'",
"echo 'Stopping existing proxy...'",
'docker compose down -v --remove-orphans',
"echo 'Starting new proxy...'",
"lsof -nt -i:80 | xargs -r kill -9",
"lsof -nt -i:443 | xargs -r kill -9",
"echo 'Starting proxy...'",
'docker compose up -d --remove-orphans',
"echo 'Proxy installed successfully...'"
], $server);

View File

@ -17,7 +17,7 @@ public function __invoke(bool $force)
$settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob');
$localhost_name = 'localhost';
if (isDev()) {
if (is_dev()) {
$localhost_name = 'testing-local-docker-container';
}
$this->server = Server::where('name', $localhost_name)->firstOrFail();
@ -49,9 +49,10 @@ public function __invoke(bool $force)
return;
}
}
private function update()
{
if (isDev()) {
if (is_dev()) {
ray("Running update on local docker container. Updating to $this->latest_version");
remote_process([
"sleep 10"

View File

@ -10,10 +10,12 @@ class Init extends Command
{
protected $signature = 'app:init';
protected $description = 'Cleanup instance related stuffs';
public function handle()
{
$this->cleanup_in_progress_application_deployments();
}
private function cleanup_in_progress_application_deployments()
{
// Cleanup any failed deployments

View File

@ -43,15 +43,16 @@ private function showHelp()
style('coolify')->color('#9333EA');
style('title-box')->apply('mt-1 px-2 py-1 bg-coolify');
render(<<<'HTML'
render(
<<<'HTML'
<div>
<div class="title-box">
Coolify
</div>
<p class="ml-1 mt-1 ">
<p class="mt-1 ml-1 ">
Demo Notify <strong class="text-coolify">=></strong> Send a demo notification to a given channel.
</p>
<p class="ml-1 mt-1 bg-coolify px-1">
<p class="px-1 mt-1 ml-1 bg-coolify">
php artisan app:demo-notify {channel}
</p>
<div class="my-1">
@ -64,7 +65,8 @@ private function showHelp()
</ul>
</div>
</div>
HTML);
HTML
);
ask(<<<'HTML'
<div class="mr-1">

View File

@ -4,8 +4,8 @@
use Illuminate\Console\Command;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
class SyncBunny extends Command
{

View File

@ -3,10 +3,14 @@
namespace App\Console;
use App\Jobs\CheckResaleLicenseJob;
use App\Jobs\CheckResaleLicenseKeys;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\InstanceApplicationsStatusJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ProxyCheckJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\CheckResaleLicenseKeys;
use App\Models\ScheduledDatabaseBackup;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -14,20 +18,47 @@ class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule): void
{
if (isDev()) {
// $schedule->call(fn() => $this->check_scheduled_backups($schedule))->everyTenSeconds();
if (is_dev()) {
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new InstanceApplicationsStatusJob)->everyMinute();
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
// $schedule->job(new CheckResaleLicenseJob)->hourly();
// $schedule->job(new DockerCleanupJob)->everyOddHour();
// $schedule->job(new InstanceAutoUpdateJob(true))->everyMinute();
} else {
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
$schedule->job(new InstanceApplicationsStatusJob)->everyMinute();
$schedule->job(new CheckResaleLicenseJob)->hourly();
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
$schedule->job(new DockerCleanupJob)->everyTenMinutes();
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes();
}
$this->check_scheduled_backups($schedule);
}
private function check_scheduled_backups($schedule)
{
ray('check_scheduled_backups');
$scheduled_backups = ScheduledDatabaseBackup::all();
if ($scheduled_backups->isEmpty()) {
ray('no scheduled backups');
return;
}
foreach ($scheduled_backups as $scheduled_backup) {
if (!$scheduled_backup->enabled) continue;
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
}
$schedule->job(new DatabaseBackupJob(
backup: $scheduled_backup
))->cron($scheduled_backup->frequency);
}
}
protected function commands(): void
{
$this->load(__DIR__ . '/Commands');

View File

@ -8,6 +8,7 @@ enum ProxyTypes: string
case NGINX = 'NGINX';
case CADDY = 'CADDY';
}
enum ProxyStatus: string
{
case EXITED = 'exited';

View File

@ -4,13 +4,12 @@
use App\Models\InstanceSettings;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
use Sentry\Laravel\Integration;
use Throwable;
class Handler extends ExceptionHandler
{
private InstanceSettings $settings;
/**
* A list of exception types with their corresponding custom log levels.
*
@ -19,7 +18,6 @@ class Handler extends ExceptionHandler
protected $levels = [
//
];
/**
* A list of the exception types that are not reported.
*
@ -28,7 +26,6 @@ class Handler extends ExceptionHandler
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed to the session on validation exceptions.
*
@ -39,6 +36,7 @@ class Handler extends ExceptionHandler
'password',
'password_confirmation',
];
private InstanceSettings $settings;
/**
* Register the exception handling callbacks for the application.
@ -47,7 +45,7 @@ public function register(): void
{
$this->reportable(function (Throwable $e) {
$this->settings = InstanceSettings::get();
if ($this->settings->do_not_track || isDev()) {
if ($this->settings->do_not_track || is_dev()) {
return;
}
Integration::captureUnhandledException($e);

View File

@ -5,15 +5,14 @@
use App\Models\ApplicationDeploymentQueue;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Spatie\Activitylog\Models\Activity;
class ApplicationController extends Controller
{
use AuthorizesRequests, ValidatesRequests;
public function configuration()
{
$project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
$project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
@ -27,9 +26,10 @@ public function configuration()
}
return view('project.application.configuration', ['application' => $application]);
}
public function deployments()
{
$project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
$project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
@ -49,7 +49,7 @@ public function deployment()
{
$deploymentUuid = request()->route('deployment_uuid');
$project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
$project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}

View File

@ -2,77 +2,89 @@
namespace App\Http\Controllers;
use App\Http\Livewire\Team\Invitations;
use App\Models\InstanceSettings;
use App\Models\Project;
use App\Models\S3Storage;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Models\Waitlist;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\DB;
use Throwable;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
public function waitlist() {
$waiting_in_line = Waitlist::whereVerified(true)->count();
return view('auth.waitlist', [
'waiting_in_line' => $waiting_in_line,
]);
}
public function subscription()
{
if (!isCloud()) {
if (!is_cloud()) {
abort(404);
}
return view('subscription', [
'settings' => InstanceSettings::get()
'settings' => InstanceSettings::get(),
]);
}
public function license()
{
if (!isCloud()) {
if (!is_cloud()) {
abort(404);
}
return view('settings.license', [
'settings' => InstanceSettings::get()
'settings' => InstanceSettings::get(),
]);
}
public function force_passoword_reset() {
return view('auth.force-password-reset');
}
public function dashboard()
{
$projects = Project::ownedByCurrentTeam()->get();
$servers = Server::ownedByCurrentTeam()->get();
$s3s = S3Storage::ownedByCurrentTeam()->get();
$resources = 0;
foreach ($projects as $project) {
$resources += $project->applications->count();
$resources += $project->postgresqls->count();
}
return view('dashboard', [
'servers' => $servers->count(),
'projects' => $projects->count(),
'resources' => $resources,
's3s' => $s3s,
]);
}
public function settings()
{
if (auth()->user()->isInstanceAdmin()) {
if (is_instance_admin()) {
$settings = InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first();
if ($database) {
$s3s = S3Storage::whereTeamId(0)->get();
}
return view('settings.configuration', [
'settings' => $settings
]);
} else {
return redirect()->route('dashboard');
}
}
public function emails()
{
if (auth()->user()->isInstanceAdmin()) {
$settings = InstanceSettings::get();
return view('settings.emails', [
'settings' => $settings
'settings' => $settings,
'database' => $database,
's3s' => $s3s ?? [],
]);
} else {
return redirect()->route('dashboard');
}
}
public function team()
{
$invitations = [];
@ -83,6 +95,23 @@ public function team()
'invitations' => $invitations,
]);
}
public function storages()
{
$s3 = S3Storage::ownedByCurrentTeam()->get();
return view('team.storages.all', [
's3' => $s3,
]);
}
public function storages_show()
{
$storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->firstOrFail();
return view('team.storages.show', [
'storage' => $storage,
]);
}
public function members()
{
$invitations = [];
@ -93,6 +122,7 @@ public function members()
'invitations' => $invitations,
]);
}
public function acceptInvitation()
{
try {
@ -115,10 +145,11 @@ public function acceptInvitation()
$invitation->delete();
abort(401);
}
} catch (\Throwable $th) {
} catch (Throwable $th) {
throw $th;
}
}
public function revokeInvitation()
{
try {
@ -132,7 +163,7 @@ public function revokeInvitation()
}
$invitation->delete();
return redirect()->route('team.show');
} catch (\Throwable $th) {
} catch (Throwable $th) {
throw $th;
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
class DatabaseController extends Controller
{
use AuthorizesRequests, ValidatesRequests;
public function configuration()
{
$project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
return view('project.database.configuration', ['database' => $database]);
}
public function executions()
{
$backup_uuid = request()->route('backup_uuid');
$project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
$backup = $database->scheduledBackups->where('uuid', $backup_uuid)->first();
if (!$backup) {
return redirect()->route('dashboard');
}
$executions = collect($backup->executions)->sortByDesc('created_at');
return view('project.database.backups.executions', [
'database' => $database,
'backup' => $backup,
'executions' => $executions,
's3s' => auth()->user()->currentTeam()->s3s,
]);
}
public function backups()
{
$project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
return view('project.database.backups.all', [
'database' => $database,
's3s' => auth()->user()->currentTeam()->s3s,
]);
}
}

View File

@ -2,7 +2,6 @@
namespace App\Http\Controllers;
use App\Http\Livewire\Server\PrivateKey;
use App\Models\Environment;
use App\Models\Project;
use App\Models\Server;
@ -16,34 +15,39 @@ public function servers()
'servers' => Server::isUsable()->get()
]);
}
public function destinations()
{
return response()->json([
'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name')
]);
}
public function projects()
{
return response()->json([
'projects' => Project::ownedByCurrentTeam()->get()
]);
}
public function environments()
{
return response()->json([
'environments' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first()->environments
]);
}
public function newProject()
{
$project = Project::firstOrCreate(
['name' => request()->query('name') ?? generate_random_name()],
['team_id' => session('currentTeam')->id]
['team_id' => auth()->user()->currentTeam()->id]
);
return response()->json([
'project_uuid' => $project->uuid
]);
}
public function newEnvironment()
{
$environment = Environment::firstOrCreate(
@ -54,6 +58,7 @@ public function newEnvironment()
'environment_name' => $environment->name,
]);
}
public function newTeam()
{
$team = Team::create(

View File

@ -3,46 +3,48 @@
namespace App\Http\Controllers;
use App\Models\Project;
use App\Models\Server;
class ProjectController extends Controller
{
public function all()
{
$teamId = session('currentTeam')->id;
$projects = Project::where('team_id', $teamId)->get();
return view('projects', ['projects' => $projects]);
return view('projects', [
'projects' => Project::ownedByCurrentTeam()->get(),
'servers' => Server::ownedByCurrentTeam()->count(),
]);
}
public function edit()
{
$projectUuid = request()->route('project_uuid');
$teamId = session('currentTeam')->id;
$teamId = auth()->user()->currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) {
return redirect()->route('dashboard');
}
return view('project.edit', ['project' => $project]);
}
public function show()
{
$projectUuid = request()->route('project_uuid');
$teamId = session('currentTeam')->id;
$teamId = auth()->user()->currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) {
return redirect()->route('dashboard');
}
$project->load(['environments']);
if (count($project->environments) == 1) {
return redirect()->route('project.resources', ['project_uuid' => $project->uuid, 'environment_name' => $project->environments->first()->name]);
}
return view('project.show', ['project' => $project]);
}
public function new()
{
$project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
$type = request()->query('type');
$destination_uuid = request()->query('destination');
$project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
@ -50,16 +52,22 @@ public function new()
if (!$environment) {
return redirect()->route('dashboard');
}
$type = request()->query('type');
if (in_array($type, DATABASE_TYPES)) {
$standalone_postgresql = create_standalone_postgresql($environment->id, $destination_uuid);
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'database_uuid' => $standalone_postgresql->uuid,
]);
}
return view('project.new', [
'type' => $type
]);
}
public function resources()
{
$project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
$project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers;
use App\Models\PrivateKey;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
class ServerController extends Controller
{
use AuthorizesRequests, ValidatesRequests;
public function new_server()
{
if (!is_cloud() || is_instance_admin()) {
return view('server.create', [
'limit_reached' => false,
'private_keys' => PrivateKey::ownedByCurrentTeam()->get(),
]);
}
$servers = auth()->user()->currentTeam()->servers->count();
$subscription = auth()->user()->currentTeam()?->subscription->type();
$limits = config('constants.limits.server')[strtolower($subscription)];
$limit_reached = true ?? $servers >= $limits[$subscription];
return view('server.create', [
'limit_reached' => $limit_reached,
'private_keys' => PrivateKey::ownedByCurrentTeam()->get(),
]);
}
}

View File

@ -37,6 +37,7 @@ class Kernel extends HttpKernel
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\CheckForcePasswordReset::class,
\App\Http\Middleware\SubscriptionValid::class,
],

View File

@ -8,19 +8,13 @@
class ActivityMonitor extends Component
{
public bool $header = false;
public string|null $header = null;
public $activityId;
public $isPollingActive = false;
protected $activity;
protected $listeners = ['newMonitorActivity'];
public function hydrateActivity()
{
$this->activity = Activity::query()
->find($this->activityId);
}
public function newMonitorActivity($activityId)
{
$this->activityId = $activityId;
@ -30,6 +24,12 @@ public function newMonitorActivity($activityId)
$this->isPollingActive = true;
}
public function hydrateActivity()
{
$this->activity = Activity::query()
->find($this->activityId);
}
public function polling()
{
$this->hydrateActivity();
@ -42,8 +42,10 @@ public function polling()
$this->setStatus(ProcessStatus::ERROR);
}
$this->isPollingActive = false;
$this->emit('activityFinished');
}
}
protected function setStatus($status)
{
$this->activity->properties = $this->activity->properties->merge([

View File

@ -19,11 +19,13 @@ class CheckLicense extends Component
'instance_id' => 'Instance Id (Do not change this)',
'settings.is_resale_license_active' => 'Is License Active',
];
public function mount()
{
$this->instance_id = config('app.id');
$this->settings = InstanceSettings::get();
}
public function submit()
{
$this->validate();

View File

@ -18,11 +18,13 @@ class Form extends Component
'destination.network' => 'network',
'destination.server.ip' => 'IP Address',
];
public function submit()
{
$this->validate();
$this->destination->save();
}
public function delete()
{
try {

View File

@ -5,6 +5,7 @@
use App\Models\Server;
use App\Models\StandaloneDocker as ModelsStandaloneDocker;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@ -27,6 +28,7 @@ class StandaloneDocker extends Component
'network' => 'network',
'server_id' => 'server'
];
public function mount()
{
if (request()->query('server_id')) {
@ -41,16 +43,17 @@ public function mount()
} else {
$this->network = new Cuid2(7);
}
$this->name = generate_random_name();
$this->name = Str::kebab("{$this->servers->first()->name}-{$this->network}");
}
private function createNetworkAndAttachToProxy()
public function generate_name()
{
instant_remote_process(['docker network create --attachable ' . $this->network], $this->server, throwError: false);
instant_remote_process(["docker network connect $this->network coolify-proxy"], $this->server, throwError: false);
$this->server = Server::find($this->server_id);
$this->name = Str::kebab("{$this->server->name}-{$this->network}");
}
public function submit()
{
$this->validate();
try {
$this->server = Server::find($this->server_id);
@ -64,7 +67,7 @@ public function submit()
'name' => $this->name,
'network' => $this->network,
'server_id' => $this->server_id,
'team_id' => session('currentTeam')->id
'team_id' => auth()->user()->currentTeam()->id
]);
}
$this->createNetworkAndAttachToProxy();
@ -73,4 +76,10 @@ public function submit()
return general_error_handler(err: $e);
}
}
private function createNetworkAndAttachToProxy()
{
instant_remote_process(['docker network create --attachable ' . $this->network], $this->server, throwError: false);
instant_remote_process(["docker network connect $this->network coolify-proxy"], $this->server, throwError: false);
}
}

View File

@ -10,6 +10,7 @@ class Show extends Component
{
public Server $server;
public Collection|array $networks = [];
public function scan()
{
$alreadyAddedNetworks = $this->server->standaloneDockers;

View File

@ -0,0 +1,41 @@
<?php
namespace App\Http\Livewire\Dev;
use App\Models\S3Storage;
use Illuminate\Support\Facades\Storage;
use Livewire\Component;
use Livewire\WithFileUploads;
class S3Test extends Component
{
use WithFileUploads;
public $s3;
public $file;
public function mount()
{
$this->s3 = S3Storage::first();
}
public function save()
{
try {
$this->validate([
'file' => 'required|max:150', // 1MB Max
]);
set_s3_target($this->s3);
$this->file->storeAs('files', $this->file->getClientOriginalName(), 'custom-s3');
$this->emit('success', 'File uploaded successfully.');
} catch (\Throwable $th) {
return general_error_handler($th, $this, false);
}
}
public function get_files()
{
set_s3_target($this->s3);
dd(Storage::disk('custom-s3')->files('files'));
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Http\Livewire;
use Illuminate\Support\Facades\Hash;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Livewire\Component;
class ForcePasswordReset extends Component
{
use WithRateLimiting;
public string $email;
public string $password;
public string $password_confirmation;
protected $rules = [
'email' => 'required|email',
'password' => 'required|min:8',
'password_confirmation' => 'required|same:password',
];
public function mount() {
$this->email = auth()->user()->email;
}
public function submit() {
try {
$this->rateLimit(10);
$this->validate();
auth()->user()->forceFill([
'password' => Hash::make($this->password),
'force_password_reset' => false,
])->save();
auth()->logout();
return redirect()->route('login')->with('status', 'Your initial password has been set.');
} catch(\Exception $e) {
return general_error_handler(err:$e, that:$this);
}
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace App\Http\Livewire;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
class License extends Component
{
public string $license;
public function submit()
{
ray('checking license');
$this->validate([
'license' => 'required'
]);
// Pretend we're checking the license
// if ($this->license === '123') {
// ray('license is valid');
// Cache::put('license_key', '123');
// return redirect()->to('/');
// } else {
// ray('license is invalid');
// }
}
}

View File

@ -3,48 +3,55 @@
namespace App\Http\Livewire\Notifications;
use App\Models\Team;
use App\Notifications\Notifications\TestNotification;
use App\Notifications\Test;
use Livewire\Component;
class DiscordSettings extends Component
{
public Team $model;
protected $rules = [
'model.discord.enabled' => 'nullable|boolean',
'model.discord.webhook_url' => 'required|url',
'model.discord_notifications.test' => 'nullable|boolean',
'model.discord_notifications.deployments' => 'nullable|boolean',
'model.discord_enabled' => 'nullable|boolean',
'model.discord_webhook_url' => 'required|url',
'model.discord_notifications_test' => 'nullable|boolean',
'model.discord_notifications_deployments' => 'nullable|boolean',
'model.discord_notifications_status_changes' => 'nullable|boolean',
'model.discord_notifications_database_backups' => 'nullable|boolean',
];
protected $validationAttributes = [
'model.discord.webhook_url' => 'Discord Webhook',
'model.discord_webhook_url' => 'Discord Webhook',
];
public function instantSave()
{
try {
$this->submit();
} catch (\Exception $e) {
$this->model->discord->enabled = false;
ray($e->getMessage());
$this->model->discord_enabled = false;
$this->validate();
}
}
public function saveModel()
{
$this->model->save();
if (is_a($this->model, Team::class)) {
session(['currentTeam' => $this->model]);
}
$this->emit('success', 'Settings saved.');
}
public function submit()
{
$this->resetErrorBag();
$this->validate();
$this->saveModel();
}
public function saveModel()
{
ray($this->model);
$this->model->save();
if (is_a($this->model, Team::class)) {
session(['currentTeam' => $this->model]);
}
$this->emit('success', 'Settings saved.');
}
public function sendTestNotification()
{
$this->model->notify(new TestNotification('discord'));
$this->model->notify(new Test);
$this->emit('success', 'Test notification sent.');
}
}

View File

@ -4,89 +4,116 @@
use App\Models\InstanceSettings;
use App\Models\Team;
use App\Notifications\Notifications\TestNotification;
use App\Notifications\Test;
use Livewire\Component;
class EmailSettings extends Component
{
public Team $model;
public string $emails;
protected $rules = [
'model.smtp.enabled' => 'nullable|boolean',
'model.smtp.from_address' => 'required|email',
'model.smtp.from_name' => 'required',
'model.smtp.recipients' => 'nullable',
'model.smtp.host' => 'required',
'model.smtp.port' => 'required',
'model.smtp.encryption' => 'nullable',
'model.smtp.username' => 'nullable',
'model.smtp.password' => 'nullable',
'model.smtp.timeout' => 'nullable',
'model.smtp.test_recipients' => 'nullable',
'model.smtp_notifications.test' => 'nullable|boolean',
'model.smtp_notifications.deployments' => 'nullable|boolean',
'model.discord_notifications.test' => 'nullable|boolean',
'model.discord_notifications.deployments' => 'nullable|boolean',
'model.smtp_enabled' => 'nullable|boolean',
'model.smtp_from_address' => 'required|email',
'model.smtp_from_name' => 'required',
'model.smtp_recipients' => 'nullable',
'model.smtp_host' => 'required',
'model.smtp_port' => 'required',
'model.smtp_encryption' => 'nullable',
'model.smtp_username' => 'nullable',
'model.smtp_password' => 'nullable',
'model.smtp_timeout' => 'nullable',
'model.smtp_notifications_test' => 'nullable|boolean',
'model.smtp_notifications_deployments' => 'nullable|boolean',
'model.smtp_notifications_status_changes' => 'nullable|boolean',
'model.smtp_notifications_database_backups' => 'nullable|boolean',
];
protected $validationAttributes = [
'model.smtp.from_address' => 'From Address',
'model.smtp.from_name' => 'From Name',
'model.smtp.recipients' => 'Recipients',
'model.smtp.host' => 'Host',
'model.smtp.port' => 'Port',
'model.smtp.encryption' => 'Encryption',
'model.smtp.username' => 'Username',
'model.smtp.password' => 'Password',
'model.smtp.test_recipients' => 'Test Recipients',
'model.smtp_from_address' => 'From Address',
'model.smtp_from_name' => 'From Name',
'model.smtp_recipients' => 'Recipients',
'model.smtp_host' => 'Host',
'model.smtp_port' => 'Port',
'model.smtp_encryption' => 'Encryption',
'model.smtp_username' => 'Username',
'model.smtp_password' => 'Password',
];
public function mount()
{
$this->decrypt();
$this->emails = auth()->user()->email;
}
private function decrypt()
{
if (data_get($this->model, 'smtp.password')) {
if (data_get($this->model, 'smtp_password')) {
try {
$this->model->smtp->password = decrypt($this->model->smtp->password);
$this->model->smtp_password = decrypt($this->model->smtp_password);
} catch (\Exception $e) {
}
}
}
public function mount()
{
$this->decrypt();
}
public function copyFromInstanceSettings()
{
$settings = InstanceSettings::get();
if ($settings->smtp->enabled) {
$this->model->smtp->enabled = true;
$this->model->smtp->from_address = $settings->smtp->from_address;
$this->model->smtp->from_name = $settings->smtp->from_name;
$this->model->smtp->recipients = $settings->smtp->recipients;
$this->model->smtp->host = $settings->smtp->host;
$this->model->smtp->port = $settings->smtp->port;
$this->model->smtp->encryption = $settings->smtp->encryption;
$this->model->smtp->username = $settings->smtp->username;
$this->model->smtp->password = $settings->smtp->password;
$this->model->smtp->timeout = $settings->smtp->timeout;
$this->model->smtp->test_recipients = $settings->smtp->test_recipients;
$this->saveModel();
if ($settings->smtp_enabled) {
$team = auth()->user()->currentTeam();
$team->update([
'smtp_enabled' => $settings->smtp_enabled,
'smtp_from_address' => $settings->smtp_from_address,
'smtp_from_name' => $settings->smtp_from_name,
'smtp_recipients' => $settings->smtp_recipients,
'smtp_host' => $settings->smtp_host,
'smtp_port' => $settings->smtp_port,
'smtp_encryption' => $settings->smtp_encryption,
'smtp_username' => $settings->smtp_username,
'smtp_password' => $settings->smtp_password,
'smtp_timeout' => $settings->smtp_timeout,
]);
$this->decrypt();
if (is_a($team, Team::class)) {
session(['currentTeam' => $team]);
}
$this->model = $team;
$this->emit('success', 'Settings saved.');
} else {
$this->emit('error', 'Instance SMTP settings are not enabled.');
}
}
public function sendTestNotification()
{
$this->model->notify(new Test($this->emails));
$this->emit('success', 'Test Email sent successfully.');
}
public function instantSave()
{
try {
$this->submit();
} catch (\Exception $e) {
$this->model->smtp_enabled = false;
$this->validate();
}
}
public function submit()
{
$this->resetErrorBag();
$this->validate();
if ($this->model->smtp->password) {
$this->model->smtp->password = encrypt($this->model->smtp->password);
if ($this->model->smtp_password) {
$this->model->smtp_password = encrypt($this->model->smtp_password);
} else {
$this->model->smtp->password = null;
$this->model->smtp_password = null;
}
$this->model->smtp->recipients = str_replace(' ', '', $this->model->smtp->recipients);
$this->model->smtp->test_recipients = str_replace(' ', '', $this->model->smtp->test_recipients);
$this->model->smtp_recipients = str_replace(' ', '', $this->model->smtp_recipients);
$this->saveModel();
}
public function saveModel()
{
$this->model->save();
@ -96,18 +123,4 @@ public function saveModel()
}
$this->emit('success', 'Settings saved.');
}
public function sendTestNotification()
{
$this->model->notify(new TestNotification('smtp'));
$this->emit('success', 'Test notification sent.');
}
public function instantSave()
{
try {
$this->submit();
} catch (\Exception $e) {
$this->model->smtp->enabled = false;
$this->validate();
}
}
}

View File

@ -20,12 +20,13 @@ class Change extends Component
'private_key.description' => 'description',
'private_key.private_key' => 'private key'
];
public function delete()
{
try {
if ($this->private_key->isEmpty()) {
$this->private_key->delete();
session('currentTeam')->privateKeys = PrivateKey::where('team_id', session('currentTeam')->id)->get();
auth()->user()->currentTeam()->privateKeys = PrivateKey::where('team_id', auth()->user()->currentTeam()->id)->get();
return redirect()->route('private-key.all');
}
$this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
@ -33,6 +34,7 @@ public function delete()
return general_error_handler(err: $e, that: $this);
}
}
public function changePrivateKey()
{
try {
@ -41,7 +43,7 @@ public function changePrivateKey()
$this->private_key->private_key .= "\n";
}
$this->private_key->save();
session('currentTeam')->privateKeys = PrivateKey::where('team_id', session('currentTeam')->id)->get();
refreshPrivateKey($this->private_key);
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}

View File

@ -7,7 +7,7 @@
class Create extends Component
{
protected string|null $from = null;
public string|null $from = null;
public string $name;
public string|null $description = null;
public string $value;
@ -19,6 +19,7 @@ class Create extends Component
'name' => 'name',
'value' => 'private Key',
];
public function createPrivateKey()
{
$this->validate();
@ -31,7 +32,7 @@ public function createPrivateKey()
'name' => $this->name,
'description' => $this->description,
'private_key' => $this->value,
'team_id' => session('currentTeam')->id
'team_id' => auth()->user()->currentTeam()->id
]);
if ($this->from === 'server') {
return redirect()->route('server.create');

View File

@ -17,12 +17,14 @@ class Form extends Component
protected $validationAttributes = [
'name' => 'name',
];
public function mount()
{
$this->userId = auth()->user()->id;
$this->name = auth()->user()->name;
$this->email = auth()->user()->email;
}
public function submit()
{

View File

@ -0,0 +1,37 @@
<?php
namespace App\Http\Livewire\Project;
use App\Models\Project;
use Livewire\Component;
class AddEmpty extends Component
{
public string $name = '';
public string $description = '';
protected $rules = [
'name' => 'required|string|min:3',
'description' => 'nullable|string',
];
protected $validationAttributes = [
'name' => 'Project Name',
'description' => 'Project Description',
];
public function submit()
{
try {
$this->validate();
$project = Project::create([
'name' => $this->name,
'description' => $this->description,
'team_id' => auth()->user()->currentTeam()->id,
]);
return redirect()->route('project.show', $project->uuid);
} catch (\Exception $e) {
general_error_handler($e, $this);
} finally {
$this->name = '';
}
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Http\Livewire\Project;
use App\Models\Environment;
use App\Models\Project;
use Livewire\Component;
class AddEnvironment extends Component
{
public Project $project;
public string $name = '';
public string $description = '';
protected $rules = [
'name' => 'required|string|min:3',
];
protected $validationAttributes = [
'name' => 'Environment Name',
];
public function submit()
{
try {
$this->validate();
$environment = Environment::create([
'name' => $this->name,
'project_id' => $this->project->id,
]);
return redirect()->route('project.resources', [
'project_uuid' => $this->project->uuid,
'environment_name' => $environment->name,
]);
} catch (\Exception $e) {
general_error_handler($e, $this);
} finally {
$this->name = '';
}
}
}

View File

@ -10,10 +10,12 @@ class DeploymentLogs extends Component
public ApplicationDeploymentQueue $application_deployment_queue;
public $isKeepAliveOn = true;
protected $listeners = ['refreshQueue'];
public function refreshQueue()
{
$this->application_deployment_queue->refresh();
}
public function polling()
{
$this->emit('deploymentFinished');

View File

@ -8,17 +8,16 @@
use App\Models\Server;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Process;
use Livewire\Component;
use Illuminate\Support\Str;
use Livewire\Component;
class DeploymentNavbar extends Component
{
protected $listeners = ['deploymentFinished'];
public ApplicationDeploymentQueue $application_deployment_queue;
public Application $application;
public Server $server;
public bool $is_debug_enabled = false;
protected $listeners = ['deploymentFinished'];
public function mount()
{
@ -26,10 +25,12 @@ public function mount()
$this->server = $this->application->destination->server;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
}
public function deploymentFinished()
{
$this->application_deployment_queue->refresh();
}
public function show_debug()
{
$this->application->settings->is_debug_enabled = !$this->application->settings->is_debug_enabled;
@ -37,6 +38,7 @@ public function show_debug()
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->emit('refreshQueue');
}
public function cancel()
{
try {

View File

@ -20,6 +20,7 @@ public function mount()
$this->current_url = url()->current();
$this->show_more();
}
private function show_more()
{
if (count($this->deployments) !== 0) {
@ -30,10 +31,12 @@ private function show_more()
return;
}
}
public function reload_deployments()
{
$this->load_deployments();
}
public function load_deployments(int|null $take = null)
{
if ($take) {

View File

@ -1,45 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Application\EnvironmentVariable;
use App\Models\Application;
use App\Models\EnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class All extends Component
{
public Application $application;
public string|null $modalId = null;
protected $listeners = ['refreshEnvs', 'submit'];
public function mount()
{
$this->modalId = new Cuid2(7);
}
public function refreshEnvs()
{
$this->application->refresh();
}
public function submit($data)
{
try {
$found = $this->application->environment_variables()->where('key', $data['key'])->first();
if ($found) {
$this->emit('error', 'Environment variable already exists.');
return;
}
EnvironmentVariable::create([
'key' => $data['key'],
'value' => $data['value'],
'is_build_time' => $data['is_build_time'],
'is_preview' => $data['is_preview'],
'application_id' => $this->application->id,
]);
$this->application->refresh();
$this->emit('success', 'Environment variable added successfully.');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
}

View File

@ -4,8 +4,8 @@
use App\Models\Application;
use App\Models\InstanceSettings;
use Livewire\Component;
use Illuminate\Support\Str;
use Livewire\Component;
use Spatie\Url\Url;
class General extends Component
@ -33,6 +33,7 @@ class General extends Component
protected $rules = [
'application.name' => 'required',
'application.description' => 'nullable',
'application.fqdn' => 'nullable',
'application.git_repository' => 'required',
'application.git_branch' => 'required',
@ -46,9 +47,11 @@ class General extends Component
'application.publish_directory' => 'nullable',
'application.ports_exposes' => 'required',
'application.ports_mappings' => 'nullable',
'application.dockerfile' => 'nullable',
];
protected $validationAttributes = [
'application.name' => 'name',
'application.description' => 'description',
'application.fqdn' => 'FQDN',
'application.git_repository' => 'Git repository',
'application.git_branch' => 'Git branch',
@ -62,7 +65,9 @@ class General extends Component
'application.publish_directory' => 'Publish directory',
'application.ports_exposes' => 'Ports exposes',
'application.ports_mappings' => 'Ports mappings',
'application.dockerfile' => 'Dockerfile',
];
public function instantSave()
{
// @TODO: find another way - if possible
@ -84,6 +89,7 @@ public function instantSave()
$this->emit('success', 'Application settings updated!');
$this->checkWildCardDomain();
}
protected function checkWildCardDomain()
{
$coolify_instance_settings = InstanceSettings::get();
@ -91,6 +97,7 @@ protected function checkWildCardDomain()
$this->global_wildcard_domain = data_get($coolify_instance_settings, 'wildcard_domain');
$this->wildcard_domain = $this->server_wildcard_domain ?? $this->global_wildcard_domain ?? null;
}
public function mount()
{
$this->is_static = $this->application->settings->is_static;
@ -102,6 +109,7 @@ public function mount()
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
$this->checkWildCardDomain();
}
public function generateGlobalRandomDomain()
{
// Set wildcard domain based on Global wildcard domain
@ -113,6 +121,7 @@ public function generateGlobalRandomDomain()
$this->application->save();
$this->emit('success', 'Application settings updated!');
}
public function generateServerRandomDomain()
{
// Set wildcard domain based on Server wildcard domain
@ -124,6 +133,7 @@ public function generateServerRandomDomain()
$this->application->save();
$this->emit('success', 'Application settings updated!');
}
public function submit()
{
try {
@ -132,6 +142,10 @@ public function submit()
$domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return Str::of($domain)->trim()->lower();
});
$port = get_port_from_dockerfile($this->application->dockerfile);
if ($port) {
$this->application->ports_exposes = $port;
}
if ($this->application->base_directory && $this->application->base_directory !== '/') {
$this->application->base_directory = rtrim($this->application->base_directory, '/');
}

View File

@ -1,9 +1,10 @@
<?php
namespace App\Http\Livewire\Application;
namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob;
use App\Jobs\ContainerStatusJob;
use App\Models\Application;
use App\Notifications\Application\StatusChanged;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@ -16,17 +17,23 @@ class Heading extends Component
public function mount()
{
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
}
public function check_status()
{
dispatch_sync(new ApplicationContainerStatusJob(
application: $this->application,
dispatch_sync(new ContainerStatusJob(
resource: $this->application,
container_name: generate_container_name($this->application->uuid),
));
$this->application->refresh();
}
public function force_deploy_without_cache()
{
$this->deploy(force_rebuild: true);
}
public function deploy(bool $force_rebuild = false)
{
$this->setDeploymentUuid();
@ -42,10 +49,13 @@ public function deploy(bool $force_rebuild = false)
'environment_name' => $this->parameters['environment_name'],
]);
}
public function force_deploy_without_cache()
protected function setDeploymentUuid()
{
$this->deploy(force_rebuild: true);
$this->deploymentUuid = new Cuid2(7);
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
}
public function stop()
{
remote_process(
@ -54,10 +64,6 @@ public function stop()
);
$this->application->status = 'stopped';
$this->application->save();
}
protected function setDeploymentUuid()
{
$this->deploymentUuid = new Cuid2(7);
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
$this->application->environment->project->team->notify(new StatusChanged($this->application));
}
}

View File

@ -17,6 +17,7 @@ class Form extends Component
protected $validationAttributes = [
'application.preview_url_template' => 'preview url template',
];
public function resetToDefault()
{
$this->application->preview_url_template = '{{pr_id}}.{{domain}}';
@ -24,6 +25,7 @@ public function resetToDefault()
$this->application->save();
$this->generate_real_url();
}
public function generate_real_url()
{
if (data_get($this->application, 'fqdn')) {
@ -32,10 +34,12 @@ public function generate_real_url()
$this->preview_url_template = Str::of($this->application->preview_url_template)->replace('{{domain}}', $host);
}
}
public function mount()
{
$this->generate_real_url();
}
public function submit()
{
$this->validate();

View File

@ -2,7 +2,7 @@
namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob;
use App\Jobs\ContainerStatusJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Illuminate\Support\Collection;
@ -20,21 +20,18 @@ class Previews extends Component
public function mount()
{
$this->pull_requests = collect();
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
}
public function loadStatus($pull_request_id)
{
dispatch(new ApplicationContainerStatusJob(
application: $this->application,
dispatch(new ContainerStatusJob(
resource: $this->application,
container_name: generate_container_name($this->application->uuid, $pull_request_id),
pull_request_id: $pull_request_id
));
}
protected function setDeploymentUuid()
{
$this->deployment_uuid = new Cuid2(7);
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
}
public function load_prs()
{
try {
@ -46,6 +43,7 @@ public function load_prs()
return general_error_handler(err: $e, that: $this);
}
}
public function deploy(int $pull_request_id, string|null $pull_request_html_url = null)
{
try {
@ -74,6 +72,13 @@ public function deploy(int $pull_request_id, string|null $pull_request_html_url
return general_error_handler(err: $e, that: $this);
}
}
protected function setDeploymentUuid()
{
$this->deployment_uuid = new Cuid2(7);
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
}
public function stop(int $pull_request_id)
{
try {
@ -87,6 +92,7 @@ public function stop(int $pull_request_id)
return general_error_handler(err: $e, that: $this);
}
}
public function previewRefresh()
{
$this->application->previews->each(function ($preview) {

View File

@ -1,60 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Application;
use App\Models\Application;
use Livewire\Component;
class ResourceLimits extends Component
{
public Application $application;
protected $rules = [
'application.limits_memory' => 'required|string',
'application.limits_memory_swap' => 'required|string',
'application.limits_memory_swappiness' => 'required|integer|min:0|max:100',
'application.limits_memory_reservation' => 'required|string',
'application.limits_cpus' => 'nullable',
'application.limits_cpuset' => 'nullable',
'application.limits_cpu_shares' => 'nullable',
];
protected $validationAttributes = [
'application.limits_memory' => 'memory',
'application.limits_memory_swap' => 'swap',
'application.limits_memory_swappiness' => 'swappiness',
'application.limits_memory_reservation' => 'reservation',
'application.limits_cpus' => 'cpus',
'application.limits_cpuset' => 'cpuset',
'application.limits_cpu_shares' => 'cpu shares',
];
public function submit()
{
try {
if (!$this->application->limits_memory) {
$this->application->limits_memory = "0";
}
if (!$this->application->limits_memory_swap) {
$this->application->limits_memory_swap = "0";
}
if (!$this->application->limits_memory_swappiness) {
$this->application->limits_memory_swappiness = "60";
}
if (!$this->application->limits_memory_reservation) {
$this->application->limits_memory_reservation = "0";
}
if (!$this->application->limits_cpus) {
$this->application->limits_cpus = "0";
}
if (!$this->application->limits_cpuset) {
$this->application->limits_cpuset = "0";
}
if (!$this->application->limits_cpu_shares) {
$this->application->limits_cpu_shares = 1024;
}
$this->validate();
$this->application->save();
$this->emit('success', 'Resource limits updated successfully.');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
}

View File

@ -3,8 +3,8 @@
namespace App\Http\Livewire\Project\Application;
use App\Models\Application;
use Livewire\Component;
use Illuminate\Support\Str;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Rollback extends Component
@ -16,8 +16,9 @@ class Rollback extends Component
public function mount()
{
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
}
public function rollbackImage($commit)
{
$deployment_uuid = new Cuid2(7);
@ -36,6 +37,7 @@ public function rollbackImage($commit)
'environment_name' => $this->parameters['environment_name'],
]);
}
public function loadImages()
{
try {

View File

@ -21,16 +21,19 @@ class Source extends Component
'application.git_branch' => 'branch',
'application.git_commit_sha' => 'commit sha',
];
private function get_private_keys()
{
$this->private_keys = PrivateKey::whereTeamId(session('currentTeam')->id)->get()->reject(function ($key) {
return $key->id == $this->application->private_key_id;
});
}
public function mount()
{
$this->get_private_keys();
}
private function get_private_keys()
{
$this->private_keys = PrivateKey::whereTeamId(auth()->user()->currentTeam()->id)->get()->reject(function ($key) {
return $key->id == $this->application->private_key_id;
});
}
public function setPrivateKey(int $private_key_id)
{
$this->application->private_key_id = $private_key_id;
@ -38,6 +41,7 @@ public function setPrivateKey(int $private_key_id)
$this->application->refresh();
$this->get_private_keys();
}
public function submit()
{
$this->validate();

View File

@ -0,0 +1,83 @@
<?php
namespace App\Http\Livewire\Project\Database;
use Livewire\Component;
class BackupEdit extends Component
{
public $backup;
public $s3s;
public array $parameters;
protected $rules = [
'backup.enabled' => 'required|boolean',
'backup.frequency' => 'required|string',
'backup.number_of_backups_locally' => 'required|integer|min:1',
'backup.save_s3' => 'required|boolean',
'backup.s3_storage_id' => 'nullable|integer',
];
protected $validationAttributes = [
'backup.enabled' => 'Enabled',
'backup.frequency' => 'Frequency',
'backup.number_of_backups_locally' => 'Number of Backups Locally',
'backup.save_s3' => 'Save to S3',
'backup.s3_storage_id' => 'S3 Storage',
];
protected $messages = [
'backup.s3_storage_id' => 'Select a S3 Storage',
];
public function mount()
{
$this->parameters = get_route_parameters();
if (is_null($this->backup->s3_storage_id)) {
$this->backup->s3_storage_id = 'default';
}
}
public function delete()
{
// TODO: Delete backup from server and add a confirmation modal
$this->backup->delete();
redirect()->route('project.database.backups.all', $this->parameters);
}
public function instantSave()
{
try {
$this->custom_validate();
$this->backup->save();
$this->backup->refresh();
$this->emit('success', 'Backup updated successfully');
} catch (\Exception $e) {
$this->emit('error', $e->getMessage());
}
}
private function custom_validate()
{
if (!is_numeric($this->backup->s3_storage_id)) {
$this->backup->s3_storage_id = null;
}
$isValid = validate_cron_expression($this->backup->frequency);
if (!$isValid) {
throw new \Exception('Invalid Cron / Human expression');
}
$this->validate();
}
public function submit()
{
ray($this->backup->s3_storage_id);
try {
$this->custom_validate();
$this->backup->save();
$this->backup->refresh();
$this->emit('success', 'Backup updated successfully');
} catch (\Exception $e) {
$this->emit('error', $e->getMessage());
}
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackupExecution;
use Livewire\Component;
class BackupExecution extends Component
{
public ScheduledDatabaseBackupExecution $execution;
public function download()
{
}
public function delete(): void
{
delete_backup_locally($this->execution->filename, $this->execution->scheduledDatabaseBackup->database->destination->server);
$this->execution->delete();
$this->emit('success', 'Backup execution deleted successfully.');
$this->emit('refreshBackupExecutions');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Livewire\Project\Database;
use Livewire\Component;
class BackupExecutions extends Component
{
public $backup;
public $executions;
protected $listeners = ['refreshBackupExecutions'];
public function refreshBackupExecutions(): void
{
$this->executions = $this->backup->executions;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Livewire\Project\Database;
use App\Jobs\DatabaseBackupJob;
use Livewire\Component;
class BackupNow extends Component
{
public $backup;
public function backup_now()
{
dispatch(new DatabaseBackupJob(
backup: $this->backup
));
$this->emit('success', 'Backup queued. It will be available in a few minutes');
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Http\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Livewire\Component;
class CreateScheduledBackup extends Component
{
public $database;
public $frequency;
public bool $enabled = true;
public bool $save_s3 = true;
public $s3_storage_id;
public $s3s;
protected $rules = [
'frequency' => 'required|string',
'save_s3' => 'required|boolean',
];
protected $validationAttributes = [
'frequency' => 'Backup Frequency',
'save_s3' => 'Save to S3',
];
public function submit(): void
{
try {
$this->validate();
$isValid = validate_cron_expression($this->frequency);
if (!$isValid) {
$this->emit('error', 'Invalid Cron / Human expression.');
return;
}
ScheduledDatabaseBackup::create([
'enabled' => true,
'frequency' => $this->frequency,
'save_s3' => $this->save_s3,
's3_storage_id' => $this->s3_storage_id,
'database_id' => $this->database->id,
'database_type' => $this->database->getMorphClass(),
'team_id' => auth()->user()->currentTeam()->id,
]);
$this->emit('refreshScheduledBackups');
} catch (\Exception $e) {
general_error_handler($e, $this);
} finally {
$this->frequency = '';
$this->save_s3 = true;
}
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Http\Livewire\Project\Database;
use App\Actions\Database\StartPostgresql;
use App\Jobs\ContainerStatusJob;
use App\Notifications\Application\StatusChanged;
use Livewire\Component;
class Heading extends Component
{
public $database;
public array $parameters;
protected $listeners = ['activityFinished'];
public function activityFinished()
{
$this->database->update([
'started_at' => now(),
]);
$this->emit('refresh');
$this->check_status();
}
public function check_status()
{
dispatch_sync(new ContainerStatusJob(
resource: $this->database,
container_name: generate_container_name($this->database->uuid),
));
$this->database->refresh();
}
public function mount()
{
$this->parameters = get_route_parameters();
}
public function stop()
{
remote_process(
["docker rm -f {$this->database->uuid}"],
$this->database->destination->server
);
$this->database->status = 'stopped';
$this->database->save();
$this->database->environment->project->team->notify(new StatusChanged($this->database));
}
public function start()
{
if ($this->database->type() === 'standalone-postgresql') {
$activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database);
$this->emit('newMonitorActivity', $activity->id);
}
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Livewire\Project\Database;
use Livewire\Component;
class InitScript extends Component
{
public array $script;
public int $index;
public string|null $filename;
public string|null $content;
protected $rules = [
'filename' => 'required|string',
'content' => 'required|string',
];
protected $validationAttributes = [
'filename' => 'Filename',
'content' => 'Content',
];
public function mount()
{
$this->index = data_get($this->script, 'index');
$this->filename = data_get($this->script, 'filename');
$this->content = data_get($this->script, 'content');
}
public function submit()
{
$this->validate();
try {
$this->script['index'] = $this->index;
$this->script['content'] = $this->content;
$this->script['filename'] = $this->filename;
$this->emitUp('save_init_script', $this->script);
} catch (Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
public function delete()
{
$this->emitUp('delete_init_script', $this->script);
}
}

View File

@ -0,0 +1,107 @@
<?php
namespace App\Http\Livewire\Project\Database\Postgresql;
use App\Models\StandalonePostgresql;
use Exception;
use Livewire\Component;
use function Aws\filter;
class General extends Component
{
public StandalonePostgresql $database;
public string $new_filename;
public string $new_content;
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
protected $rules = [
'database.name' => 'required',
'database.description' => 'nullable',
'database.postgres_user' => 'required',
'database.postgres_password' => 'required',
'database.postgres_db' => 'required',
'database.postgres_initdb_args' => 'nullable',
'database.postgres_host_auth_method' => 'nullable',
'database.init_scripts' => 'nullable',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
];
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.postgres_user' => 'Postgres User',
'database.postgres_password' => 'Postgres Password',
'database.postgres_db' => 'Postgres DB',
'database.postgres_initdb_args' => 'Postgres Initdb Args',
'database.postgres_host_auth_method' => 'Postgres Host Auth Method',
'database.init_scripts' => 'Init Scripts',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
];
public function save_init_script($script)
{
$this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']);
$this->database->init_scripts = array_merge($this->database->init_scripts, [$script]);
$this->database->save();
$this->emit('success', 'Init script saved successfully.');
}
public function delete_init_script($script)
{
$collection = collect($this->database->init_scripts);
$found = $collection->firstWhere('filename', $script['filename']);
if ($found) {
ray($collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray());
$this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray();
$this->database->save();
$this->refresh();
$this->emit('success', 'Init script deleted successfully.');
return;
}
}
public function refresh(): void
{
$this->database->refresh();
}
public function save_new_init_script()
{
$this->validate([
'new_filename' => 'required|string',
'new_content' => 'required|string',
]);
$found = collect($this->database->init_scripts)->firstWhere('filename', $this->new_filename);
if ($found) {
$this->emit('error', 'Filename already exists.');
return;
}
if (!isset($this->database->init_scripts)) {
$this->database->init_scripts = [];
}
$this->database->init_scripts = array_merge($this->database->init_scripts, [
[
'index' => count($this->database->init_scripts),
'filename' => $this->new_filename,
'content' => $this->new_content,
]
]);
$this->database->save();
$this->emit('success', 'Init script added successfully.');
$this->new_content = '';
$this->new_filename = '';
}
public function submit()
{
try {
$this->validate();
$this->database->save();
$this->emit('success', 'Database updated successfully.');
} catch (Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Livewire\Project\Database;
use Livewire\Component;
class ScheduledBackups extends Component
{
public $database;
public $parameters;
protected $listeners = ['refreshScheduledBackups'];
public function mount(): void
{
$this->parameters = get_route_parameters();
}
public function delete($scheduled_backup_id): void
{
$this->database->scheduledBackups->find($scheduled_backup_id)->delete();
$this->emit('success', 'Scheduled backup deleted successfully.');
$this->refreshScheduledBackups();
}
public function refreshScheduledBackups(): void
{
ray('refreshScheduledBackups');
$this->database->refresh();
}
}

View File

@ -12,8 +12,9 @@ class DeleteEnvironment extends Component
public function mount()
{
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
}
public function delete()
{
$this->validate([

View File

@ -12,8 +12,9 @@ class DeleteProject extends Component
public function mount()
{
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
}
public function delete()
{
$this->validate([

View File

@ -11,7 +11,7 @@ public function createEmptyProject()
{
$project = Project::create([
'name' => generate_random_name(),
'team_id' => session('currentTeam')->id,
'team_id' => auth()->user()->currentTeam()->id,
]);
return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production']);
}

View File

@ -5,16 +5,14 @@
use App\Models\Application;
use App\Models\GithubApp;
use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Livewire\Component;
class GithubPrivateRepository extends Component
{
public $current_step = 'github_apps';
public $github_apps;
public GithubApp $github_app;
public $parameters;
@ -29,52 +27,23 @@ class GithubPrivateRepository extends Component
public string $selected_branch_name = 'main';
public string $token;
protected int $page = 1;
public $repositories;
public int $total_repositories_count = 0;
public $branches;
public int $total_branches_count = 0;
public int $port = 3000;
public bool $is_static = false;
public string|null $publish_directory = null;
protected int $page = 1;
public function mount()
{
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->repositories = $this->branches = collect();
$this->github_apps = GithubApp::private();
}
protected function loadRepositoryByPage()
{
$response = Http::withToken($this->token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100&page={$this->page}");
$json = $response->json();
if ($response->status() !== 200) {
return $this->emit('error', $json['message']);
}
if ($json['total_count'] === 0) {
return;
}
$this->total_repositories_count = $json['total_count'];
$this->repositories = $this->repositories->concat(collect($json['repositories']));
}
protected function loadBranchByPage()
{
Log::info('Loading page ' . $this->page);
$response = Http::withToken($this->token)->get("{$this->github_app->api_url}/repos/{$this->selected_repository_owner}/{$this->selected_repository_repo}/branches?per_page=100&page={$this->page}");
$json = $response->json();
if ($response->status() !== 200) {
return $this->emit('error', $json['message']);
}
$this->total_branches_count = count($json);
$this->branches = $this->branches->concat(collect($json));
}
public function loadRepositories($github_app_id)
{
$this->repositories = collect();
@ -90,7 +59,24 @@ public function loadRepositories($github_app_id)
}
}
$this->selected_repository_id = $this->repositories[0]['id'];
$this->current_step = 'repository';
}
protected function loadRepositoryByPage()
{
$response = Http::withToken($this->token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100&page={$this->page}");
$json = $response->json();
if ($response->status() !== 200) {
return $this->emit('error', $json['message']);
}
if ($json['total_count'] === 0) {
return;
}
$this->total_repositories_count = $json['total_count'];
$this->repositories = $this->repositories->concat(collect($json['repositories']));
}
public function loadBranches()
{
$this->selected_repository_owner = $this->repositories->where('id', $this->selected_repository_id)->first()['owner']['login'];
@ -105,6 +91,20 @@ public function loadBranches()
}
}
}
protected function loadBranchByPage()
{
ray('Loading page ' . $this->page);
$response = Http::withToken($this->token)->get("{$this->github_app->api_url}/repos/{$this->selected_repository_owner}/{$this->selected_repository_repo}/branches?per_page=100&page={$this->page}");
$json = $response->json();
if ($response->status() !== 200) {
return $this->emit('error', $json['message']);
}
$this->total_branches_count = count($json);
$this->branches = $this->branches->concat(collect($json));
}
public function submit()
{
try {
@ -148,6 +148,7 @@ public function submit()
return general_error_handler(err: $e, that: $this);
}
}
public function instantSave()
{
if ($this->is_static) {

View File

@ -14,6 +14,7 @@
class GithubPrivateRepositoryDeployKey extends Component
{
public $current_step = 'private_keys';
public $parameters;
public $query;
public $private_keys;
@ -26,14 +27,7 @@ class GithubPrivateRepositoryDeployKey extends Component
public null|string $publish_directory = null;
public string $repository_url;
private object $repository_url_parsed;
public string $branch;
private GithubApp|GitlabApp $git_source;
private string $git_host;
private string $git_repository;
private string $git_branch;
protected $rules = [
'repository_url' => 'required|url',
'branch' => 'required|string',
@ -48,15 +42,22 @@ class GithubPrivateRepositoryDeployKey extends Component
'is_static' => 'Is static',
'publish_directory' => 'Publish directory',
];
private object $repository_url_parsed;
private GithubApp|GitlabApp $git_source;
private string $git_host;
private string $git_repository;
private string $git_branch;
public function mount()
{
if (isDev()) {
if (is_dev()) {
$this->repository_url = 'https://github.com/coollabsio/coolify-examples';
}
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->private_keys = PrivateKey::where('team_id', session('currentTeam')->id)->where('id', '!=', 0)->get();
$this->private_keys = PrivateKey::where('team_id', auth()->user()->currentTeam()->id)->where('id', '!=', 0)->get();
}
public function instantSave()
{
if ($this->is_static) {
@ -67,29 +68,13 @@ public function instantSave()
$this->publish_directory = null;
}
}
public function setPrivateKey($private_key_id)
{
$this->private_key_id = $private_key_id;
}
private function get_git_source()
{
$this->repository_url_parsed = Url::fromString($this->repository_url);
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
if ($this->branch) {
$this->git_branch = $this->branch;
} else {
$this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main';
$this->current_step = 'repository';
}
if ($this->git_host == 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
} elseif ($this->git_host == 'gitlab.com') {
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
} elseif ($this->git_host == 'bitbucket.org') {
// Not supported yet
}
}
public function submit()
{
$this->validate();
@ -136,4 +121,24 @@ public function submit()
return general_error_handler(err: $e, that: $this);
}
}
private function get_git_source()
{
$this->repository_url_parsed = Url::fromString($this->repository_url);
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
if ($this->branch) {
$this->git_branch = $this->branch;
} else {
$this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main';
}
if ($this->git_host == 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
} elseif ($this->git_host == 'gitlab.com') {
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
} elseif ($this->git_host == 'bitbucket.org') {
// Not supported yet
}
}
}

View File

@ -15,13 +15,10 @@
class PublicGitRepository extends Component
{
public string $repository_url;
private object $repository_url_parsed;
public int $port = 3000;
public string $type;
public $parameters;
public $query;
public bool $branch_found = false;
public string $selected_branch = 'main';
public bool $is_static = false;
@ -29,11 +26,6 @@ class PublicGitRepository extends Component
public string $git_branch = 'main';
public int $rate_limit_remaining = 0;
public $rate_limit_reset = 0;
private GithubApp|GitlabApp $git_source;
private string $git_host;
private string $git_repository;
protected $rules = [
'repository_url' => 'required|url',
'port' => 'required|numeric',
@ -46,13 +38,18 @@ class PublicGitRepository extends Component
'is_static' => 'static',
'publish_directory' => 'publish directory',
];
private object $repository_url_parsed;
private GithubApp|GitlabApp $git_source;
private string $git_host;
private string $git_repository;
public function mount()
{
if (isDev()) {
if (is_dev()) {
$this->repository_url = 'https://github.com/coollabsio/coolify-examples';
$this->port = 3000;
}
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
$this->query = request()->query();
}
@ -67,12 +64,7 @@ public function instantSave()
}
$this->emit('success', 'Application settings updated!');
}
private function get_branch()
{
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = git_api(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->branch_found = true;
}
public function load_branch()
{
$this->branch_found = false;
@ -82,7 +74,9 @@ public function load_branch()
$this->get_git_source();
try {
$this->get_branch();
$this->selected_branch = $this->git_branch;
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
if (!$this->branch_found && $this->git_branch == 'main') {
@ -94,6 +88,7 @@ public function load_branch()
}
}
}
private function get_git_source()
{
$this->repository_url_parsed = Url::fromString($this->repository_url);
@ -109,6 +104,14 @@ private function get_git_source()
// Not supported yet
}
}
private function get_branch()
{
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = git_api(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->branch_found = true;
}
public function submit()
{
try {

View File

@ -0,0 +1,51 @@
<?php
namespace App\Http\Livewire\Project\New;
use App\Models\Server;
use Livewire\Component;
class Select extends Component
{
public $current_step = 'type';
public string $type;
public string $server_id;
public string $destination_uuid;
public $servers = [];
public $destinations = [];
public array $parameters;
public function mount()
{
$this->parameters = get_route_parameters();
}
public function set_type(string $type)
{
$this->type = $type;
$this->current_step = 'servers';
}
public function set_server(Server $server)
{
$this->server_id = $server->id;
$this->destinations = $server->destinations();
$this->current_step = 'destinations';
}
public function set_destination(string $destination_uuid)
{
$this->destination_uuid = $destination_uuid;
redirect()->route('project.resources.new', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name'],
'type' => $this->type,
'destination' => $this->destination_uuid,
]);
}
public function load_servers()
{
$this->servers = Server::ownedByCurrentTeam()->get();
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Http\Livewire\Project\New;
use App\Models\Application;
use App\Models\GithubApp;
use App\Models\Project;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class SimpleDockerfile extends Component
{
public string $dockerfile = '';
public array $parameters;
public array $query;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (is_dev()) {
$this->dockerfile = 'FROM nginx
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
';
}
}
public function submit()
{
$this->validate([
'dockerfile' => 'required'
]);
$destination_uuid = $this->query['destination'];
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (!$destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
}
if (!$destination) {
throw new \Exception('Destination not found. What?!');
}
$destination_class = $destination->getMorphClass();
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$port = get_port_from_dockerfile($this->dockerfile);
$application = Application::create([
'name' => 'dockerfile-' . new Cuid2(7),
'repository_project_id' => 0,
'git_repository' => "coollabsio/coolify",
'git_branch' => 'main',
'build_pack' => 'dockerfile',
'dockerfile' => $this->dockerfile,
'ports_exposes' => $port,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'source_id' => 0,
'source_type' => GithubApp::class
]);
redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
}
}

View File

@ -1,28 +1,28 @@
<?php
namespace App\Http\Livewire\Project\Application;
namespace App\Http\Livewire\Project\Shared;
use App\Models\Application;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Danger extends Component
{
public Application $application;
public $resource;
public array $parameters;
public string|null $modalId = null;
public function mount()
{
$this->modalId = new Cuid2(7);
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
}
public function delete()
{
$destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first();
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
instant_remote_process(["docker rm -f {$this->application->uuid}"], $destination->server);
$this->application->delete();
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $destination->server);
$this->resource->delete();
return redirect()->route('project.resources', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name']

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Livewire\Project\Application;
namespace App\Http\Livewire\Project\Shared;
use Livewire\Component;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Livewire\Project\Application\EnvironmentVariable;
namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use Livewire\Component;
@ -23,10 +23,12 @@ class Add extends Component
'value' => 'value',
'is_build_time' => 'build',
];
public function mount()
{
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
}
public function submit()
{
ray('submitting');
@ -39,6 +41,7 @@ public function submit()
]);
$this->clear();
}
public function clear()
{
$this->key = '';

View File

@ -0,0 +1,52 @@
<?php
namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class All extends Component
{
public $resource;
public string|null $modalId = null;
protected $listeners = ['refreshEnvs', 'submit'];
public function mount()
{
$this->modalId = new Cuid2(7);
}
public function refreshEnvs()
{
$this->resource->refresh();
}
public function submit($data)
{
try {
$found = $this->resource->environment_variables()->where('key', $data['key'])->first();
if ($found) {
$this->emit('error', 'Environment variable already exists.');
return;
}
$environment = new EnvironmentVariable();
$environment->key = $data['key'];
$environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time'];
$environment->is_preview = $data['is_preview'];
if ($this->resource->type() === 'application') {
$environment->application_id = $this->resource->id;
}
if ($this->resource->type() === 'standalone-postgresql') {
$environment->standalone_postgresql_id = $this->resource->id;
}
$environment->save();
$this->resource->refresh();
$this->emit('success', 'Environment variable added successfully.');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Livewire\Project\Application\EnvironmentVariable;
namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Livewire\Component;
@ -21,10 +21,16 @@ class Show extends Component
'value' => 'value',
'is_build_time' => 'build',
];
public function mount()
{
$this->modalId = new Cuid2(7);
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
}
public function instantSave()
{
$this->submit();
}
public function submit()
{
@ -32,6 +38,7 @@ public function submit()
$this->env->save();
$this->emit('success', 'Environment variable updated successfully.');
}
public function delete()
{
$this->env->delete();

View File

@ -0,0 +1,60 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use Livewire\Component;
class ResourceLimits extends Component
{
public $resource;
protected $rules = [
'resource.limits_memory' => 'required|string',
'resource.limits_memory_swap' => 'required|string',
'resource.limits_memory_swappiness' => 'required|integer|min:0|max:100',
'resource.limits_memory_reservation' => 'required|string',
'resource.limits_cpus' => 'nullable',
'resource.limits_cpuset' => 'nullable',
'resource.limits_cpu_shares' => 'nullable',
];
protected $validationAttributes = [
'resource.limits_memory' => 'memory',
'resource.limits_memory_swap' => 'swap',
'resource.limits_memory_swappiness' => 'swappiness',
'resource.limits_memory_reservation' => 'reservation',
'resource.limits_cpus' => 'cpus',
'resource.limits_cpuset' => 'cpuset',
'resource.limits_cpu_shares' => 'cpu shares',
];
public function submit()
{
try {
if (!$this->resource->limits_memory) {
$this->resource->limits_memory = "0";
}
if (!$this->resource->limits_memory_swap) {
$this->resource->limits_memory_swap = "0";
}
if (!$this->resource->limits_memory_swappiness) {
$this->resource->limits_memory_swappiness = "60";
}
if (!$this->resource->limits_memory_reservation) {
$this->resource->limits_memory_reservation = "0";
}
if (!$this->resource->limits_cpus) {
$this->resource->limits_cpus = "0";
}
if (!$this->resource->limits_cpuset) {
$this->resource->limits_cpuset = "0";
}
if (!$this->resource->limits_cpu_shares) {
$this->resource->limits_cpu_shares = 1024;
}
$this->validate();
$this->resource->save();
$this->emit('success', 'Resource limits updated successfully.');
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Livewire\Project\Application\Storages;
namespace App\Http\Livewire\Project\Shared\Storages;
use Livewire\Component;
@ -22,10 +22,12 @@ class Add extends Component
'mount_path' => 'mount',
'host_path' => 'host',
];
public function mount()
{
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
}
public function submit()
{
$this->validate();
@ -35,6 +37,7 @@ public function submit()
'host_path' => $this->host_path,
]);
}
public function clear()
{
$this->name = '';

View File

@ -1,19 +1,20 @@
<?php
namespace App\Http\Livewire\Project\Application\Storages;
namespace App\Http\Livewire\Project\Shared\Storages;
use App\Models\Application;
use App\Models\LocalPersistentVolume;
use Livewire\Component;
class All extends Component
{
public Application $application;
public $resource;
protected $listeners = ['refreshStorages', 'submit'];
public function refreshStorages()
{
$this->application->refresh();
$this->resource->refresh();
}
public function submit($data)
{
try {
@ -21,10 +22,10 @@ public function submit($data)
'name' => $data['name'],
'mount_path' => $data['mount_path'],
'host_path' => $data['host_path'],
'resource_id' => $this->application->id,
'resource_type' => Application::class,
'resource_id' => $this->resource->id,
'resource_type' => $this->resource->getMorphClass(),
]);
$this->application->refresh();
$this->resource->refresh();
$this->emit('success', 'Storage added successfully');
$this->emit('clearAddStorage');
} catch (\Exception $e) {

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Livewire\Project\Application\Storages;
namespace App\Http\Livewire\Project\Shared\Storages;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@ -19,16 +19,19 @@ class Show extends Component
'mount_path' => 'mount',
'host_path' => 'host',
];
public function mount()
{
$this->modalId = new Cuid2(7);
}
public function submit()
{
$this->validate();
$this->storage->save();
$this->emit('success', 'Storage updated successfully');
}
public function delete()
{
$this->storage->delete();

View File

@ -2,7 +2,6 @@
namespace App\Http\Livewire;
use App\Enums\ActivityTypes;
use App\Models\Server;
use Livewire\Component;
@ -20,6 +19,7 @@ class RunCommand extends Component
'server' => 'server',
'command' => 'command',
];
public function mount($servers)
{
$this->servers = $servers;

View File

@ -5,7 +5,6 @@
use App\Actions\Server\InstallDocker;
use App\Models\Server;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Form extends Component
{
@ -14,7 +13,6 @@ class Form extends Component
public $dockerVersion;
public string|null $wildcard_domain = null;
public int $cleanup_after_percentage;
public string|null $modalId = null;
protected $rules = [
'server.name' => 'required|min:6',
@ -35,43 +33,35 @@ class Form extends Component
'server.settings.is_reachable' => 'is reachable',
'server.settings.is_part_of_swarm' => 'is part of swarm'
];
public function mount()
{
$this->modalId = new Cuid2(7);
$this->wildcard_domain = $this->server->settings->wildcard_domain;
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
}
public function installDocker()
{
$activity = resolve(InstallDocker::class)($this->server, session('currentTeam'));
$activity = resolve(InstallDocker::class)($this->server, auth()->user()->currentTeam());
$this->emit('newMonitorActivity', $activity->id);
}
public function validateServer()
{
try {
$this->uptime = instant_remote_process(['uptime'], $this->server);
if ($this->uptime) {
$this->server->settings->is_reachable = true;
$this->server->settings->save();
} else {
$this->uptime = 'Server not reachable.';
throw new \Exception('Server not reachable.');
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server);
if ($uptime) {
$this->uptime = $uptime;
}
$this->dockerVersion = instant_remote_process(['docker version|head -2|grep -i version'], $this->server, false);
if (!$this->dockerVersion) {
$this->dockerVersion = 'Not installed.';
} else {
$this->server->settings->is_usable = true;
$this->server->settings->save();
if ($dockerVersion) {
$this->dockerVersion = $dockerVersion;
$this->emit('proxyStatusUpdated');
}
} catch (\Exception $e) {
$this->server->settings->is_reachable = false;
$this->server->settings->is_usable = false;
$this->server->settings->save();
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
}
}
public function delete()
{
if (!$this->server->isEmpty()) {
@ -81,6 +71,7 @@ public function delete()
$this->server->delete();
redirect()->route('server.all');
}
public function submit()
{
$this->validate();

View File

@ -2,13 +2,13 @@
namespace App\Http\Livewire\Server\New;
use App\Models\PrivateKey;
use App\Models\Server;
use Livewire\Component;
class ByIp extends Component
{
public $private_keys;
public $limit_reached;
public int|null $private_key_id = null;
public $new_private_key_name;
public $new_private_key_description;
@ -35,19 +35,23 @@ class ByIp extends Component
'user' => 'user',
'port' => 'port',
];
public function mount()
{
$this->name = generate_random_name();
$this->private_key_id = $this->private_keys->first()->id;
}
public function setPrivateKey(string $private_key_id)
{
$this->private_key_id = $private_key_id;
}
public function instantSave()
{
$this->emit('success', 'Application settings updated!');
}
public function submit()
{
$this->validate();
@ -61,7 +65,7 @@ public function submit()
'ip' => $this->ip,
'user' => $this->user,
'port' => $this->port,
'team_id' => session('currentTeam')->id,
'team_id' => auth()->user()->currentTeam()->id,
'private_key_id' => $this->private_key_id,
]);
$server->settings->is_part_of_swarm = $this->is_part_of_swarm;

View File

@ -3,7 +3,6 @@
namespace App\Http\Livewire\Server;
use App\Models\Server;
use Illuminate\Support\Facades\Storage;
use Livewire\Component;
use Masmerise\Toaster\Toaster;
@ -13,35 +12,33 @@ class PrivateKey extends Component
public $privateKeys;
public $parameters;
public function checkConnection()
{
try {
$uptime = instant_remote_process(['uptime'], $this->server);
if ($uptime) {
Toaster::success('Server is reachable with this private key.');
$this->server->settings->is_reachable = true;
$this->server->settings->is_usable = true;
}
} catch (\Exception $e) {
$this->server->settings->is_reachable = false;
$this->server->settings->is_usable = false;
$this->server->settings->save();
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
}
}
public function setPrivateKey($private_key_id)
{
$this->server->update([
'private_key_id' => $private_key_id
]);
// Delete the old ssh mux file to force a new one to be created
Storage::disk('ssh-mux')->delete($this->server->muxFilename());
refreshPrivateKey($this->server->privateKey);
$this->server->refresh();
$this->checkConnection();
}
public function checkConnection()
{
try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server);
if ($uptime) {
Toaster::success('Server is reachable with this private key.');
}
if ($dockerVersion) {
Toaster::success('Server is usable for Coolify.');
}
} catch (\Exception $e) {
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
}
}
public function mount()
{
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
}
}

View File

@ -2,9 +2,9 @@
namespace App\Http\Livewire\Server;
use App\Actions\Proxy\CheckProxySettingsInSync;
use App\Actions\Proxy\CheckConfigurationSync;
use App\Actions\Proxy\SaveConfigurationSync;
use App\Enums\ProxyTypes;
use Illuminate\Support\Str;
use App\Models\Server;
use Livewire\Component;
@ -16,69 +16,61 @@ class Proxy extends Component
public $proxy_settings = null;
public string|null $redirect_url = null;
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration'];
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
public function mount()
{
$this->redirect_url = $this->server->proxy->redirect_url;
}
public function proxyStatusUpdated()
{
$this->server->refresh();
}
public function switchProxy()
public function change_proxy()
{
$this->server->proxy = null;
$this->server->save();
$this->emit('proxyStatusUpdated');
}
public function setProxy(string $proxy_type)
public function select_proxy(string $proxy_type)
{
$this->server->proxy->type = $proxy_type;
$this->server->proxy->status = 'exited';
$this->server->save();
$this->emit('proxyStatusUpdated');
}
public function stopProxy()
{
instant_remote_process([
"docker rm -f coolify-proxy",
], $this->server);
$this->server->proxy->status = 'exited';
$this->server->save();
$this->emit('proxyStatusUpdated');
}
public function saveConfiguration()
public function submit()
{
try {
$proxy_path = config('coolify.proxy_config_path');
$this->proxy_settings = Str::of($this->proxy_settings)->trim()->value;
$docker_compose_yml_base64 = base64_encode($this->proxy_settings);
$this->server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings);
$this->server->proxy->redirect_url = $this->redirect_url;
$this->server->save();
instant_remote_process([
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $this->server);
$this->server->refresh();
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
$this->emit('success', 'Proxy configuration saved.');
} catch (\Exception $e) {
return general_error_handler(err: $e);
}
}
public function resetProxy()
public function reset_proxy_configuration()
{
try {
$this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server, true);
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server, true);
} catch (\Exception $e) {
return general_error_handler(err: $e);
}
}
public function checkProxySettingsInSync()
public function load_proxy_configuration()
{
try {
$this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server);
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server);
} catch (\Exception $e) {
return general_error_handler(err: $e);
}

View File

@ -5,28 +5,24 @@
use App\Actions\Proxy\StartProxy;
use App\Models\Server;
use Livewire\Component;
use Str;
class Deploy extends Component
{
public Server $server;
public $proxy_settings = null;
protected $listeners = ['proxyStatusUpdated'];
public function proxyStatusUpdated()
{
$this->server->refresh();
}
public function deploy()
public function start_proxy()
{
if (
$this->server->proxy->last_applied_settings &&
$this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings
) {
$this->saveConfiguration($this->server);
$this->emit('saveConfiguration', $server);
}
$activity = resolve(StartProxy::class)($this->server);
$this->emit('newMonitorActivity', $activity->id);
}
public function stop()
{
instant_remote_process([
@ -36,8 +32,4 @@ public function stop()
$this->server->save();
$this->emit('proxyStatusUpdated');
}
private function saveConfiguration(Server $server)
{
$this->emit('saveConfiguration', $server);
}
}

View File

@ -9,20 +9,13 @@
class Status extends Component
{
public Server $server;
protected $listeners = ['proxyStatusUpdated'];
public function proxyStatusUpdated()
public function get_status()
{
$this->server->refresh();
}
public function proxyStatus()
{
try {
dispatch_sync(new ProxyContainerStatusJob(
server: $this->server
));
$this->server->refresh();
$this->emit('proxyStatusUpdated');
} catch (\Exception $e) {
ray($e->getMessage());
}
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace App\Http\Livewire\Settings;
use App\Jobs\DatabaseBackupJob;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use Livewire\Component;
class Backup extends Component
{
public InstanceSettings $settings;
public $s3s;
public StandalonePostgresql|null|array $database = [];
public ScheduledDatabaseBackup|null|array $backup = [];
public $executions = [];
protected $rules = [
'database.uuid' => 'required',
'database.name' => 'required',
'database.description' => 'nullable',
'database.postgres_user' => 'required',
'database.postgres_password' => 'required',
];
protected $validationAttributes = [
'database.uuid' => 'uuid',
'database.name' => 'name',
'database.description' => 'description',
'database.postgres_user' => 'postgres user',
'database.postgres_password' => 'postgres password',
];
public function mount()
{
$this->backup = $this->database?->scheduledBackups->first() ?? [];
$this->executions = $this->backup?->executions ?? [];
}
public function add_coolify_database()
{
$server = Server::find(0);
$out = instant_remote_process(['docker inspect coolify-db'], $server);
$envs = format_docker_envs_to_json($out);
$postgres_password = $envs['POSTGRES_PASSWORD'];
$postgres_user = $envs['POSTGRES_USER'];
$postgres_db = $envs['POSTGRES_DB'];
$this->database = StandalonePostgresql::create([
'id' => 0,
'name' => 'coolify-db',
'description' => 'Coolify database',
'postgres_user' => $postgres_user,
'postgres_password' => $postgres_password,
'postgres_db' => $postgres_db,
'status' => 'running',
'destination_type' => 'App\Models\StandaloneDocker',
'destination_id' => 0,
]);
$this->backup = ScheduledDatabaseBackup::create([
'id' => 0,
'enabled' => true,
'save_s3' => false,
'frequency' => '0 0 * * *',
'database_id' => $this->database->id,
'database_type' => 'App\Models\StandalonePostgresql',
'team_id' => auth()->user()->currentTeam()->id,
]);
$this->database->refresh();
$this->backup->refresh();
ray($this->backup);
$this->s3s = S3Storage::whereTeamId(0)->get();
}
public function backup_now()
{
dispatch(new DatabaseBackupJob(
backup: $this->backup
));
$this->emit('success', 'Backup queued. It will be available in a few minutes');
}
public function submit()
{
$this->emit('success', 'Backup updated successfully');
}
}

View File

@ -31,6 +31,7 @@ class Configuration extends Component
'settings.public_port_min' => 'Public port min',
'settings.public_port_max' => 'Public port max',
];
public function mount()
{
$this->do_not_track = $this->settings->do_not_track;
@ -38,6 +39,7 @@ public function mount()
$this->is_registration_enabled = $this->settings->is_registration_enabled;
$this->next_channel = $this->settings->next_channel;
}
public function instantSave()
{
$this->settings->do_not_track = $this->do_not_track;
@ -47,6 +49,21 @@ public function instantSave()
$this->settings->save();
$this->emit('success', 'Settings updated!');
}
public function submit()
{
$this->resetErrorBag();
if ($this->settings->public_port_min > $this->settings->public_port_max) {
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
return;
}
$this->validate();
$this->settings->save();
$this->server = Server::findOrFail(0);
$this->setup_instance_fqdn();
$this->emit('success', 'Instance settings updated successfully!');
}
private function setup_instance_fqdn()
{
$file = "$this->dynamic_config_path/coolify.yaml";
@ -110,6 +127,7 @@ private function setup_instance_fqdn()
dispatch(new ProxyStartJob($this->server));
}
}
private function save_configuration_to_disk(array $traefik_dynamic_conf, string $file)
{
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
@ -128,17 +146,4 @@ private function save_configuration_to_disk(array $traefik_dynamic_conf, string
ray($yaml);
}
}
public function submit()
{
$this->resetErrorBag();
if ($this->settings->public_port_min > $this->settings->public_port_max) {
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
return;
}
$this->validate();
$this->settings->save();
$this->server = Server::findOrFail(0);
$this->setup_instance_fqdn();
$this->emit('success', 'Instance settings updated successfully!');
}
}

View File

@ -3,78 +3,80 @@
namespace App\Http\Livewire\Settings;
use App\Models\InstanceSettings;
use App\Notifications\TransactionalEmails\TestEmail;
use Illuminate\Support\Facades\Notification;
use App\Notifications\TransactionalEmails\Test;
use Livewire\Component;
class Email extends Component
{
public InstanceSettings $settings;
public string $emails;
protected $rules = [
'settings.smtp.enabled' => 'nullable|boolean',
'settings.smtp.host' => 'required',
'settings.smtp.port' => 'required|numeric',
'settings.smtp.encryption' => 'nullable',
'settings.smtp.username' => 'nullable',
'settings.smtp.password' => 'nullable',
'settings.smtp.timeout' => 'nullable',
'settings.smtp.test_recipients' => 'nullable',
'settings.smtp.from_address' => 'required|email',
'settings.smtp.from_name' => 'required',
'settings.smtp_enabled' => 'nullable|boolean',
'settings.smtp_host' => 'required',
'settings.smtp_port' => 'required|numeric',
'settings.smtp_encryption' => 'nullable',
'settings.smtp_username' => 'nullable',
'settings.smtp_password' => 'nullable',
'settings.smtp_timeout' => 'nullable',
'settings.smtp_from_address' => 'required|email',
'settings.smtp_from_name' => 'required',
];
protected $validationAttributes = [
'settings.smtp.from_address' => 'From Address',
'settings.smtp.from_name' => 'From Name',
'settings.smtp.recipients' => 'Recipients',
'settings.smtp.host' => 'Host',
'settings.smtp.port' => 'Port',
'settings.smtp.encryption' => 'Encryption',
'settings.smtp.username' => 'Username',
'settings.smtp.password' => 'Password',
'settings.smtp.test_recipients' => 'Test Recipients',
'settings.smtp_from_address' => 'From Address',
'settings.smtp_from_name' => 'From Name',
'settings.smtp_recipients' => 'Recipients',
'settings.smtp_host' => 'Host',
'settings.smtp_port' => 'Port',
'settings.smtp_encryption' => 'Encryption',
'settings.smtp_username' => 'Username',
'settings.smtp_password' => 'Password',
];
public function mount()
{
$this->decrypt();
$this->emails = auth()->user()->email;
}
private function decrypt()
{
if (data_get($this->settings, 'smtp_password')) {
try {
$this->settings->smtp_password = decrypt($this->settings->smtp_password);
} catch (\Exception $e) {
}
}
}
public function instantSave()
{
try {
$this->submit();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
$this->settings->smtp->enabled = false;
$this->settings->smtp_enabled = false;
$this->validate();
}
}
public function testNotification()
{
$this->settings->notify(new TestEmail);
$this->emit('success', 'Test email sent.');
}
private function decrypt()
{
if (data_get($this->settings, 'smtp.password')) {
try {
$this->settings->smtp->password = decrypt($this->settings->smtp->password);
} catch (\Exception $e) {
}
}
}
public function submit()
{
$this->resetErrorBag();
$this->validate();
if ($this->settings->smtp->password) {
$this->settings->smtp->password = encrypt($this->settings->smtp->password);
if ($this->settings->smtp_password) {
$this->settings->smtp_password = encrypt($this->settings->smtp_password);
} else {
$this->settings->smtp->password = null;
$this->settings->smtp_password = null;
}
$this->settings->smtp->test_recipients = str_replace(' ', '', $this->settings->smtp->test_recipients);
$this->settings->save();
$this->emit('success', 'Transaction email settings updated successfully.');
$this->decrypt();
}
public function sendTestNotification()
{
$this->settings->notify(new Test($this->emails));
$this->emit('success', 'Test email sent.');
}
}

View File

@ -34,12 +34,14 @@ class Change extends Component
'github_app.webhook_secret' => 'nullable',
'github_app.is_system_wide' => 'required|bool',
];
public function mount()
{
$this->webhook_endpoint = $this->ipv4;
$this->parameters = getRouteParameters();
$this->parameters = get_route_parameters();
$this->is_system_wide = $this->github_app->is_system_wide;
}
public function submit()
{
try {
@ -49,6 +51,7 @@ public function submit()
return general_error_handler(err: $e, that: $this);
}
}
public function instantSave()
{
}

View File

@ -19,6 +19,7 @@ public function mount()
{
$this->name = generate_random_name();
}
public function createGitHubApp()
{
try {
@ -39,7 +40,7 @@ public function createGitHubApp()
'custom_user' => $this->custom_user,
'custom_port' => $this->custom_port,
'is_system_wide' => $this->is_system_wide,
'team_id' => session('currentTeam')->id,
'team_id' => auth()->user()->currentTeam()->id,
]);
redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Exception $e) {

View File

@ -8,10 +8,12 @@
class SwitchTeam extends Component
{
public string $selectedTeamId = 'default';
public function updatedSelectedTeamId()
{
$this->switch_to($this->selectedTeamId);
}
public function switch_to($team_id)
{
if (!auth()->user()->teams->contains($team_id)) {

View File

@ -18,6 +18,7 @@ class Create extends Component
'name' => 'name',
'description' => 'description',
];
public function submit()
{
$this->validate();

View File

@ -9,7 +9,7 @@ class Delete extends Component
{
public function delete()
{
$currentTeam = session('currentTeam');
$currentTeam = auth()->user()->currentTeam();
$currentTeam->delete();
$team = auth()->user()->teams()->first();

View File

@ -4,7 +4,6 @@
use App\Models\Team;
use Livewire\Component;
use Masmerise\Toaster\Toaster;
class Form extends Component
{
@ -17,10 +16,12 @@ class Form extends Component
'team.name' => 'name',
'team.description' => 'description',
];
public function mount()
{
$this->team = session('currentTeam');
$this->team = auth()->user()->currentTeam();
}
public function submit()
{
$this->validate();

View File

@ -9,13 +9,15 @@ class Invitations extends Component
{
public $invitations;
protected $listeners = ['refreshInvitations'];
public function refreshInvitations()
{
$this->invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get();
}
public function deleteInvitation(int $invitation_id)
{
TeamInvitation::find($invitation_id)->delete();
$this->refreshInvitations();
}
public function refreshInvitations()
{
$this->invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get();
}
}

View File

@ -4,7 +4,7 @@
use App\Models\TeamInvitation;
use App\Models\User;
use App\Notifications\TransactionalEmails\InvitationLinkEmail;
use App\Notifications\TransactionalEmails\InvitationLink;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@ -12,14 +12,17 @@ class InviteLink extends Component
{
public string $email;
public string $role = 'member';
public function mount()
{
$this->email = isDev() ? 'test3@example.com' : '';
$this->email = is_dev() ? 'test3@example.com' : '';
}
public function viaEmail()
{
$this->generate_invite_link(isEmail: true);
}
private function generate_invite_link(bool $isEmail = false)
{
try {
@ -32,9 +35,9 @@ private function generate_invite_link(bool $isEmail = false)
return general_error_handler(that: $this, customErrorMessage: "$this->email must be registered first (or activate transactional emails to invite via email).");
}
$member_emails = session('currentTeam')->members()->get()->pluck('email');
$member_emails = auth()->user()->currentTeam()->members()->get()->pluck('email');
if ($member_emails->contains($this->email)) {
return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . session('currentTeam')->name . ".");
return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . auth()->user()->currentTeam()->name . ".");
}
$invitation = TeamInvitation::whereEmail($this->email);
@ -50,7 +53,7 @@ private function generate_invite_link(bool $isEmail = false)
}
TeamInvitation::firstOrCreate([
'team_id' => session('currentTeam')->id,
'team_id' => auth()->user()->currentTeam()->id,
'uuid' => $uuid,
'email' => $this->email,
'role' => $this->role,
@ -58,7 +61,7 @@ private function generate_invite_link(bool $isEmail = false)
'via' => $isEmail ? 'email' : 'link',
]);
if ($isEmail) {
$user->first()->notify(new InvitationLinkEmail());
$user->first()->notify(new InvitationLink);
$this->emit('success', 'Invitation sent via email successfully.');
} else {
$this->emit('success', 'Invitation link generated.');
@ -72,6 +75,7 @@ private function generate_invite_link(bool $isEmail = false)
return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message);
}
}
public function viaLink()
{
$this->generate_invite_link();

View File

@ -8,19 +8,22 @@
class Member extends Component
{
public User $member;
public function makeAdmin()
{
$this->member->teams()->updateExistingPivot(session('currentTeam')->id, ['role' => 'admin']);
$this->member->teams()->updateExistingPivot(auth()->user()->currentTeam()->id, ['role' => 'admin']);
$this->emit('reloadWindow');
}
public function makeReadonly()
{
$this->member->teams()->updateExistingPivot(session('currentTeam')->id, ['role' => 'member']);
$this->member->teams()->updateExistingPivot(auth()->user()->currentTeam()->id, ['role' => 'member']);
$this->emit('reloadWindow');
}
public function remove()
{
$this->member->teams()->detach(session('currentTeam'));
$this->member->teams()->detach(auth()->user()->currentTeam());
$this->emit('reloadWindow');
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace App\Http\Livewire\Team\Storage;
use App\Models\S3Storage;
use Livewire\Component;
class Create extends Component
{
public string $name;
public string $description;
public string $region = 'us-east-1';
public string $key;
public string $secret;
public string $bucket;
public string $endpoint;
public S3Storage $storage;
protected $rules = [
'name' => 'nullable|min:3|max:255',
'description' => 'nullable|min:3|max:255',
'region' => 'required|max:255',
'key' => 'required|max:255',
'secret' => 'required|max:255',
'bucket' => 'required|max:255',
'endpoint' => 'nullable|url|max:255',
];
protected $validationAttributes = [
'name' => 'Name',
'description' => 'Description',
'region' => 'Region',
'key' => 'Key',
'secret' => "Secret",
'bucket' => 'Bucket',
'endpoint' => 'Endpoint',
];
public function mount()
{
if (is_dev()) {
$this->name = 'Local MinIO';
$this->description = 'Local MinIO';
$this->key = 'minioadmin';
$this->secret = 'minioadmin';
$this->bucket = 'local';
$this->endpoint = 'http://coolify-minio:9000';
}
}
public function submit()
{
try {
$this->validate();
$this->storage = new S3Storage();
$this->storage->name = $this->name;
$this->storage->description = $this->description ?? null;
$this->storage->region = $this->region;
$this->storage->key = $this->key;
$this->storage->secret = $this->secret;
$this->storage->bucket = $this->bucket;
if (empty($this->endpoint)) {
$this->storage->endpoint = "https://s3.{$this->region}.amazonaws.com";
} else {
$this->storage->endpoint = $this->endpoint;
}
$this->storage->team_id = auth()->user()->currentTeam()->id;
$this->storage->testConnection();
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
$this->storage->save();
return redirect()->route('team.storages.show', $this->storage->uuid);
} catch (\Throwable $th) {
return general_error_handler($th, $this);
}
}
private function test_s3_connection()
{
try {
$this->storage->testConnection();
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
} catch (\Throwable $th) {
return general_error_handler($th, $this);
}
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Http\Livewire\Team\Storage;
use App\Models\S3Storage;
use Livewire\Component;
class Form extends Component
{
public S3Storage $storage;
protected $rules = [
'storage.name' => 'nullable|min:3|max:255',
'storage.description' => 'nullable|min:3|max:255',
'storage.region' => 'required|max:255',
'storage.key' => 'required|max:255',
'storage.secret' => 'required|max:255',
'storage.bucket' => 'required|max:255',
'storage.endpoint' => 'required|url|max:255',
];
protected $validationAttributes = [
'storage.name' => 'Name',
'storage.description' => 'Description',
'storage.region' => 'Region',
'storage.key' => 'Key',
'storage.secret' => "Secret",
'storage.bucket' => 'Bucket',
'storage.endpoint' => 'Endpoint',
];
public function test_s3_connection()
{
try {
$this->storage->testConnection();
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
} catch (\Throwable $th) {
return general_error_handler($th, $this);
}
}
public function delete()
{
try {
$this->storage->delete();
return redirect()->route('team.storages.all');
} catch (\Throwable $th) {
return general_error_handler($th, $this);
}
}
public function submit()
{
$this->validate();
try {
$this->storage->testConnection();
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
$this->storage->save();
$this->emit('success', 'Storage settings saved.');
} catch (\Throwable $th) {
return general_error_handler($th, $this);
}
}
}

View File

@ -4,8 +4,8 @@
use App\Actions\Server\UpdateCoolify;
use App\Models\InstanceSettings;
use Masmerise\Toaster\Toaster;
use Livewire\Component;
use Masmerise\Toaster\Toaster;
class Upgrade extends Component
{
@ -18,7 +18,7 @@ public function checkUpdate()
$this->latestVersion = get_latest_version_of_coolify();
$currentVersion = config('version');
version_compare($currentVersion, $this->latestVersion, '<') ? $this->isUpgradeAvailable = true : $this->isUpgradeAvailable = false;
if (isDev()) {
if (is_dev()) {
$this->isUpgradeAvailable = true;
}
$settings = InstanceSettings::get();
@ -27,6 +27,7 @@ public function checkUpdate()
$this->latestVersion = 'next';
}
}
public function upgrade()
{
try {

View File

@ -0,0 +1,54 @@
<?php
namespace App\Http\Livewire;
use App\Jobs\SendConfirmationForWaitlistJob;
use App\Models\User;
use App\Models\Waitlist as ModelsWaitlist;
use Livewire\Component;
class Waitlist extends Component
{
public string $email;
public int $waiting_in_line = 0;
protected $rules = [
'email' => 'required|email',
];
public function mount()
{
if (is_dev()) {
$this->email = 'test@example.com';
}
}
public function submit()
{
$this->validate();
try {
$already_registered = User::whereEmail($this->email)->first();
if ($already_registered) {
$this->emit('success', 'You are already registered (Thank you 💜).');
return;
}
$found = ModelsWaitlist::where('email', $this->email)->first();
if ($found) {
if (!$found->verified) {
$this->emit('error', 'You are already on the waitlist. <br>Please check your email to verify your email address.');
return;
}
$this->emit('error', 'You are already on the waitlist.');
return;
}
$waitlist = ModelsWaitlist::create([
'email' => $this->email,
'type' => 'registration',
]);
$this->emit('success', 'You have been added to the waitlist.');
dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid));
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
}

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