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: on:
push: push:
branches: ["main", "v4"] branches: [ "main", "next" ]
paths: paths:
- .github/workflows/coolify-builder.yml - .github/workflows/coolify-helper.yml
- docker/coolify-builder/Dockerfile - docker/coolify-helper/Dockerfile
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify-builder" IMAGE_NAME: "coollabsio/coolify-helper"
jobs: jobs:
amd64: amd64:
@ -30,12 +30,12 @@ jobs:
with: with:
no-cache: true no-cache: true
context: . context: .
file: docker/coolify-builder/Dockerfile file: docker/coolify-helper/Dockerfile
platforms: linux/amd64 platforms: linux/amd64
push: true push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
aarch64: aarch64:
runs-on: [self-hosted, arm64] runs-on: [ self-hosted, arm64 ]
permissions: permissions:
contents: read contents: read
packages: write packages: write
@ -52,7 +52,7 @@ jobs:
with: with:
no-cache: true no-cache: true
context: . context: .
file: docker/coolify-builder/Dockerfile file: docker/coolify-helper/Dockerfile
platforms: linux/aarch64 platforms: linux/aarch64
push: true push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64
@ -61,7 +61,7 @@ jobs:
permissions: permissions:
contents: read contents: read
packages: write packages: write
needs: [amd64, aarch64] needs: [ amd64, aarch64 ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -77,4 +77,4 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Create & publish manifest - name: Create & publish manifest
run: | run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest

View File

@ -15,6 +15,7 @@ class PrepareCoolifyTask
{ {
protected Activity $activity; protected Activity $activity;
protected CoolifyTaskArgs $remoteProcessArgs; protected CoolifyTaskArgs $remoteProcessArgs;
public function __construct(CoolifyTaskArgs $remoteProcessArgs) public function __construct(CoolifyTaskArgs $remoteProcessArgs)
{ {
$this->remoteProcessArgs = $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; $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 public function __invoke(): ProcessResult
{ {
$this->time_start = hrtime(true); $this->time_start = hrtime(true);
@ -83,15 +105,6 @@ public function __invoke(): ProcessResult
return $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 protected function getCommand(): string
{ {
$user = $this->activity->getExtraProperty('user'); $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) public function encodeOutput($type, $output)
{ {
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR); $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); 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)) { $description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
return ''; if ($description === null || count($description) === 0) {
return 1;
} }
return end($description)['order'] + 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("");
} }
/** /**
@ -171,11 +178,4 @@ protected function isAfterLastThrottle()
return ($this->current_time - $this->throttle_interval_ms) > $this->last_write_at; 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; namespace App\Actions\Fortify;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\Team;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\CreatesNewUsers; use Laravel\Fortify\Contracts\CreatesNewUsers;
@ -19,13 +16,12 @@ class CreateNewUser implements CreatesNewUsers
/** /**
* Validate and create a newly registered user. * Validate and create a newly registered user.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function create(array $input): User public function create(array $input): User
{ {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
if (!$settings->is_registration_enabled) { if (!$settings->is_registration_enabled) {
Log::info('Registration is disabled');
abort(403); abort(403);
} }
Validator::make($input, [ Validator::make($input, [

View File

@ -14,7 +14,7 @@ class ResetUserPassword implements ResetsUserPasswords
/** /**
* Validate and reset the user's forgotten password. * Validate and reset the user's forgotten password.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function reset(User $user, array $input): void public function reset(User $user, array $input): void
{ {

View File

@ -14,7 +14,7 @@ class UpdateUserPassword implements UpdatesUserPasswords
/** /**
* Validate and update the user's password. * Validate and update the user's password.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function update(User $user, array $input): void public function update(User $user, array $input): void
{ {

View File

@ -13,7 +13,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
/** /**
* Validate and update the given user's profile information. * Validate and update the given user's profile information.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
public function update(User $user, array $input): void public function update(User $user, array $input): void
{ {
@ -29,8 +29,10 @@ public function update(User $user, array $input): void
], ],
])->validateWithBag('updateProfileInformation'); ])->validateWithBag('updateProfileInformation');
if ($input['email'] !== $user->email && if (
$user instanceof MustVerifyEmail) { $input['email'] !== $user->email &&
$user instanceof MustVerifyEmail
) {
$this->updateVerifiedUser($user, $input); $this->updateVerifiedUser($user, $input);
} else { } else {
$user->forceFill([ $user->forceFill([
@ -43,7 +45,7 @@ public function update(User $user, array $input): void
/** /**
* Update the given verified user's profile information. * Update the given verified user's profile information.
* *
* @param array<string, string> $input * @param array<string, string> $input
*/ */
protected function updateVerifiedUser(User $user, array $input): void protected function updateVerifiedUser(User $user, array $input): void
{ {

View File

@ -4,7 +4,6 @@
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Visus\Cuid2\Cuid2;
class CheckResaleLicense class CheckResaleLicense
{ {
@ -12,41 +11,52 @@ public function __invoke()
{ {
try { try {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
$instance_id = config('app.id'); $settings->update([
'is_resale_license_active' => false,
]);
if (is_dev()) {
return;
}
if (!$settings->resale_license) { if (!$settings->resale_license) {
return; 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([ $data = Http::withHeaders([
'Accept' => 'application/json', 'Accept' => 'application/json',
])->post('https://api.lemonsqueezy.com/v1/licenses/validate', [ ])->get("$base_url/lemon/validate", [
'license_key' => $settings->resale_license, 'license_key' => $settings->resale_license,
'instance_name' => $instance_id, 'instance_id' => $instance_id,
])->throw()->json(); ])->json();
$product_id = (int)data_get($data, 'meta.product_id'); if (data_get($data, 'valid') === true && data_get($data, 'license_key.status') === 'active') {
$valid_product_id = (int)config('coolify.lemon_squeezy_product_id'); ray('Valid & active license key');
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();
}
$settings->update([ $settings->update([
'is_resale_license_active' => true, 'is_resale_license_active' => true,
]); ]);
return; 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) { } catch (\Throwable $th) {
ray($th); ray($th);
$settings->update([ $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\ProxyStatus;
use App\Enums\ProxyTypes; use App\Enums\ProxyTypes;
use App\Models\Server; use App\Models\Server;
use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Spatie\Activitylog\Models\Activity;
class StartProxy class StartProxy
{ {
@ -18,8 +18,7 @@ public function __invoke(Server $server): Activity
$server->proxy->status = ProxyStatus::EXITED->value; $server->proxy->status = ProxyStatus::EXITED->value;
$server->save(); $server->save();
} }
$proxy_path = config('coolify.proxy_config_path'); $proxy_path = get_proxy_path();
$networks = collect($server->standaloneDockers)->map(function ($docker) { $networks = collect($server->standaloneDockers)->map(function ($docker) {
return $docker['network']; return $docker['network'];
})->unique(); })->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"; 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([ $configuration = resolve(CheckConfigurationSync::class)($server);
"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;
}
$docker_compose_yml_base64 = base64_encode($configuration); $docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; $server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save(); $server->save();
$activity = remote_process([ $activity = remote_process([
"echo 'Creating required Docker networks...'", "echo 'Creating required Docker networks...'",
...$create_networks_command, ...$create_networks_command,
"mkdir -p $proxy_path",
"cd $proxy_path", "cd $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
"echo 'Creating Docker Compose file...'", "echo 'Creating Docker Compose file...'",
"echo 'Pulling docker image...'", "echo 'Pulling docker image...'",
'docker compose pull -q', 'docker compose pull -q',
"echo 'Stopping old proxy...'", "echo 'Stopping existing proxy...'",
'docker compose down -v --remove-orphans', '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', 'docker compose up -d --remove-orphans',
"echo 'Proxy installed successfully...'" "echo 'Proxy installed successfully...'"
], $server); ], $server);

View File

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

View File

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

View File

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

View File

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

View File

@ -3,10 +3,14 @@
namespace App\Console; namespace App\Console;
use App\Jobs\CheckResaleLicenseJob; 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\InstanceAutoUpdateJob;
use App\Jobs\ProxyCheckJob; use App\Jobs\ProxyCheckJob;
use App\Jobs\DockerCleanupJob; use App\Models\ScheduledDatabaseBackup;
use App\Jobs\CheckResaleLicenseKeys;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -14,20 +18,47 @@ class Kernel extends ConsoleKernel
{ {
protected function schedule(Schedule $schedule): void 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->command('horizon:snapshot')->everyMinute();
$schedule->job(new InstanceApplicationsStatusJob)->everyMinute();
$schedule->job(new ProxyCheckJob)->everyFiveMinutes(); $schedule->job(new ProxyCheckJob)->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
// $schedule->job(new CheckResaleLicenseJob)->hourly(); // $schedule->job(new CheckResaleLicenseJob)->hourly();
// $schedule->job(new DockerCleanupJob)->everyOddHour(); // $schedule->job(new DockerCleanupJob)->everyOddHour();
// $schedule->job(new InstanceAutoUpdateJob(true))->everyMinute(); // $schedule->job(new InstanceAutoUpdateJob(true))->everyMinute();
} else { } else {
$schedule->command('horizon:snapshot')->everyFiveMinutes(); $schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
$schedule->job(new InstanceApplicationsStatusJob)->everyMinute();
$schedule->job(new CheckResaleLicenseJob)->hourly(); $schedule->job(new CheckResaleLicenseJob)->hourly();
$schedule->job(new ProxyCheckJob)->everyFiveMinutes(); $schedule->job(new ProxyCheckJob)->everyFiveMinutes();
$schedule->job(new DockerCleanupJob)->everyTenMinutes(); $schedule->job(new DockerCleanupJob)->everyTenMinutes();
$schedule->job(new InstanceAutoUpdateJob)->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 protected function commands(): void
{ {
$this->load(__DIR__ . '/Commands'); $this->load(__DIR__ . '/Commands');

View File

@ -9,7 +9,7 @@
class ServerMetadata extends Data class ServerMetadata extends Data
{ {
public function __construct( public function __construct(
public ?ProxyTypes $type, public ?ProxyTypes $type,
public ?ProxyStatus $status public ?ProxyStatus $status
) { ) {
} }

View File

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

View File

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

View File

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

View File

@ -2,77 +2,89 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Livewire\Team\Invitations;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\Project; use App\Models\Project;
use App\Models\S3Storage;
use App\Models\Server; use App\Models\Server;
use App\Models\StandalonePostgresql;
use App\Models\TeamInvitation; use App\Models\TeamInvitation;
use App\Models\User; use App\Models\User;
use App\Models\Waitlist;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController; use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\DB; use Throwable;
class Controller extends BaseController class Controller extends BaseController
{ {
use AuthorizesRequests, ValidatesRequests; 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() public function subscription()
{ {
if (!isCloud()) { if (!is_cloud()) {
abort(404); abort(404);
} }
return view('subscription', [ return view('subscription', [
'settings' => InstanceSettings::get() 'settings' => InstanceSettings::get(),
]); ]);
} }
public function license() public function license()
{ {
if (!isCloud()) { if (!is_cloud()) {
abort(404); abort(404);
} }
return view('settings.license', [ return view('settings.license', [
'settings' => InstanceSettings::get() 'settings' => InstanceSettings::get(),
]); ]);
} }
public function force_passoword_reset() {
return view('auth.force-password-reset');
}
public function dashboard() public function dashboard()
{ {
$projects = Project::ownedByCurrentTeam()->get(); $projects = Project::ownedByCurrentTeam()->get();
$servers = Server::ownedByCurrentTeam()->get(); $servers = Server::ownedByCurrentTeam()->get();
$s3s = S3Storage::ownedByCurrentTeam()->get();
$resources = 0; $resources = 0;
foreach ($projects as $project) { foreach ($projects as $project) {
$resources += $project->applications->count(); $resources += $project->applications->count();
$resources += $project->postgresqls->count();
} }
return view('dashboard', [ return view('dashboard', [
'servers' => $servers->count(), 'servers' => $servers->count(),
'projects' => $projects->count(), 'projects' => $projects->count(),
'resources' => $resources, 'resources' => $resources,
's3s' => $s3s,
]); ]);
} }
public function settings() public function settings()
{ {
if (auth()->user()->isInstanceAdmin()) { if (is_instance_admin()) {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first();
if ($database) {
$s3s = S3Storage::whereTeamId(0)->get();
}
return view('settings.configuration', [ return view('settings.configuration', [
'settings' => $settings 'settings' => $settings,
]); 'database' => $database,
} else { 's3s' => $s3s ?? [],
return redirect()->route('dashboard');
}
}
public function emails()
{
if (auth()->user()->isInstanceAdmin()) {
$settings = InstanceSettings::get();
return view('settings.emails', [
'settings' => $settings
]); ]);
} else { } else {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
} }
public function team() public function team()
{ {
$invitations = []; $invitations = [];
@ -83,6 +95,23 @@ public function team()
'invitations' => $invitations, '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() public function members()
{ {
$invitations = []; $invitations = [];
@ -93,6 +122,7 @@ public function members()
'invitations' => $invitations, 'invitations' => $invitations,
]); ]);
} }
public function acceptInvitation() public function acceptInvitation()
{ {
try { try {
@ -115,10 +145,11 @@ public function acceptInvitation()
$invitation->delete(); $invitation->delete();
abort(401); abort(401);
} }
} catch (\Throwable $th) { } catch (Throwable $th) {
throw $th; throw $th;
} }
} }
public function revokeInvitation() public function revokeInvitation()
{ {
try { try {
@ -132,7 +163,7 @@ public function revokeInvitation()
} }
$invitation->delete(); $invitation->delete();
return redirect()->route('team.show'); return redirect()->route('team.show');
} catch (\Throwable $th) { } catch (Throwable $th) {
throw $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; namespace App\Http\Controllers;
use App\Http\Livewire\Server\PrivateKey;
use App\Models\Environment; use App\Models\Environment;
use App\Models\Project; use App\Models\Project;
use App\Models\Server; use App\Models\Server;
@ -16,34 +15,39 @@ public function servers()
'servers' => Server::isUsable()->get() 'servers' => Server::isUsable()->get()
]); ]);
} }
public function destinations() public function destinations()
{ {
return response()->json([ return response()->json([
'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name') 'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name')
]); ]);
} }
public function projects() public function projects()
{ {
return response()->json([ return response()->json([
'projects' => Project::ownedByCurrentTeam()->get() 'projects' => Project::ownedByCurrentTeam()->get()
]); ]);
} }
public function environments() public function environments()
{ {
return response()->json([ return response()->json([
'environments' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first()->environments 'environments' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first()->environments
]); ]);
} }
public function newProject() public function newProject()
{ {
$project = Project::firstOrCreate( $project = Project::firstOrCreate(
['name' => request()->query('name') ?? generate_random_name()], ['name' => request()->query('name') ?? generate_random_name()],
['team_id' => session('currentTeam')->id] ['team_id' => auth()->user()->currentTeam()->id]
); );
return response()->json([ return response()->json([
'project_uuid' => $project->uuid 'project_uuid' => $project->uuid
]); ]);
} }
public function newEnvironment() public function newEnvironment()
{ {
$environment = Environment::firstOrCreate( $environment = Environment::firstOrCreate(
@ -54,6 +58,7 @@ public function newEnvironment()
'environment_name' => $environment->name, 'environment_name' => $environment->name,
]); ]);
} }
public function newTeam() public function newTeam()
{ {
$team = Team::create( $team = Team::create(

View File

@ -3,46 +3,48 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Project; use App\Models\Project;
use App\Models\Server;
class ProjectController extends Controller class ProjectController extends Controller
{ {
public function all() public function all()
{ {
$teamId = session('currentTeam')->id; return view('projects', [
'projects' => Project::ownedByCurrentTeam()->get(),
$projects = Project::where('team_id', $teamId)->get(); 'servers' => Server::ownedByCurrentTeam()->count(),
return view('projects', ['projects' => $projects]); ]);
} }
public function edit() public function edit()
{ {
$projectUuid = request()->route('project_uuid'); $projectUuid = request()->route('project_uuid');
$teamId = session('currentTeam')->id; $teamId = auth()->user()->currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first(); $project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) { if (!$project) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
return view('project.edit', ['project' => $project]); return view('project.edit', ['project' => $project]);
} }
public function show() public function show()
{ {
$projectUuid = request()->route('project_uuid'); $projectUuid = request()->route('project_uuid');
$teamId = session('currentTeam')->id; $teamId = auth()->user()->currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first(); $project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) { if (!$project) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
$project->load(['environments']); $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]); return view('project.show', ['project' => $project]);
} }
public function new() 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) { if (!$project) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
@ -50,16 +52,22 @@ public function new()
if (!$environment) { if (!$environment) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
if (in_array($type, DATABASE_TYPES)) {
$type = request()->query('type'); $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', [ return view('project.new', [
'type' => $type 'type' => $type
]); ]);
} }
public function resources() 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) { if (!$project) {
return redirect()->route('dashboard'); 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, \Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class, \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\CheckForcePasswordReset::class,
\App\Http\Middleware\SubscriptionValid::class, \App\Http\Middleware\SubscriptionValid::class,
], ],

View File

@ -8,19 +8,13 @@
class ActivityMonitor extends Component class ActivityMonitor extends Component
{ {
public bool $header = false; public string|null $header = null;
public $activityId; public $activityId;
public $isPollingActive = false; public $isPollingActive = false;
protected $activity; protected $activity;
protected $listeners = ['newMonitorActivity']; protected $listeners = ['newMonitorActivity'];
public function hydrateActivity()
{
$this->activity = Activity::query()
->find($this->activityId);
}
public function newMonitorActivity($activityId) public function newMonitorActivity($activityId)
{ {
$this->activityId = $activityId; $this->activityId = $activityId;
@ -30,6 +24,12 @@ public function newMonitorActivity($activityId)
$this->isPollingActive = true; $this->isPollingActive = true;
} }
public function hydrateActivity()
{
$this->activity = Activity::query()
->find($this->activityId);
}
public function polling() public function polling()
{ {
$this->hydrateActivity(); $this->hydrateActivity();
@ -42,8 +42,10 @@ public function polling()
$this->setStatus(ProcessStatus::ERROR); $this->setStatus(ProcessStatus::ERROR);
} }
$this->isPollingActive = false; $this->isPollingActive = false;
$this->emit('activityFinished');
} }
} }
protected function setStatus($status) protected function setStatus($status)
{ {
$this->activity->properties = $this->activity->properties->merge([ $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)', 'instance_id' => 'Instance Id (Do not change this)',
'settings.is_resale_license_active' => 'Is License Active', 'settings.is_resale_license_active' => 'Is License Active',
]; ];
public function mount() public function mount()
{ {
$this->instance_id = config('app.id'); $this->instance_id = config('app.id');
$this->settings = InstanceSettings::get(); $this->settings = InstanceSettings::get();
} }
public function submit() public function submit()
{ {
$this->validate(); $this->validate();

View File

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

View File

@ -5,6 +5,7 @@
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker as ModelsStandaloneDocker; use App\Models\StandaloneDocker as ModelsStandaloneDocker;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
@ -27,6 +28,7 @@ class StandaloneDocker extends Component
'network' => 'network', 'network' => 'network',
'server_id' => 'server' 'server_id' => 'server'
]; ];
public function mount() public function mount()
{ {
if (request()->query('server_id')) { if (request()->query('server_id')) {
@ -41,16 +43,17 @@ public function mount()
} else { } else {
$this->network = new Cuid2(7); $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); $this->server = Server::find($this->server_id);
instant_remote_process(["docker network connect $this->network coolify-proxy"], $this->server, throwError: false); $this->name = Str::kebab("{$this->server->name}-{$this->network}");
} }
public function submit() public function submit()
{ {
$this->validate(); $this->validate();
try { try {
$this->server = Server::find($this->server_id); $this->server = Server::find($this->server_id);
@ -64,7 +67,7 @@ public function submit()
'name' => $this->name, 'name' => $this->name,
'network' => $this->network, 'network' => $this->network,
'server_id' => $this->server_id, 'server_id' => $this->server_id,
'team_id' => session('currentTeam')->id 'team_id' => auth()->user()->currentTeam()->id
]); ]);
} }
$this->createNetworkAndAttachToProxy(); $this->createNetworkAndAttachToProxy();
@ -73,4 +76,10 @@ public function submit()
return general_error_handler(err: $e); 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 Server $server;
public Collection|array $networks = []; public Collection|array $networks = [];
public function scan() public function scan()
{ {
$alreadyAddedNetworks = $this->server->standaloneDockers; $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; namespace App\Http\Livewire\Notifications;
use App\Models\Team; use App\Models\Team;
use App\Notifications\Notifications\TestNotification; use App\Notifications\Test;
use Livewire\Component; use Livewire\Component;
class DiscordSettings extends Component class DiscordSettings extends Component
{ {
public Team $model; public Team $model;
protected $rules = [ protected $rules = [
'model.discord.enabled' => 'nullable|boolean', 'model.discord_enabled' => 'nullable|boolean',
'model.discord.webhook_url' => 'required|url', 'model.discord_webhook_url' => 'required|url',
'model.discord_notifications.test' => 'nullable|boolean', 'model.discord_notifications_test' => 'nullable|boolean',
'model.discord_notifications.deployments' => 'nullable|boolean', 'model.discord_notifications_deployments' => 'nullable|boolean',
'model.discord_notifications_status_changes' => 'nullable|boolean',
'model.discord_notifications_database_backups' => 'nullable|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'model.discord.webhook_url' => 'Discord Webhook', 'model.discord_webhook_url' => 'Discord Webhook',
]; ];
public function instantSave() public function instantSave()
{ {
try { try {
$this->submit(); $this->submit();
} catch (\Exception $e) { } catch (\Exception $e) {
$this->model->discord->enabled = false; ray($e->getMessage());
$this->model->discord_enabled = false;
$this->validate(); $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() public function submit()
{ {
$this->resetErrorBag(); $this->resetErrorBag();
$this->validate(); $this->validate();
$this->saveModel(); $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() public function sendTestNotification()
{ {
$this->model->notify(new TestNotification('discord')); $this->model->notify(new Test);
$this->emit('success', 'Test notification sent.'); $this->emit('success', 'Test notification sent.');
} }
} }

View File

@ -4,89 +4,116 @@
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\Team; use App\Models\Team;
use App\Notifications\Notifications\TestNotification; use App\Notifications\Test;
use Livewire\Component; use Livewire\Component;
class EmailSettings extends Component class EmailSettings extends Component
{ {
public Team $model; public Team $model;
public string $emails;
protected $rules = [ protected $rules = [
'model.smtp.enabled' => 'nullable|boolean', 'model.smtp_enabled' => 'nullable|boolean',
'model.smtp.from_address' => 'required|email', 'model.smtp_from_address' => 'required|email',
'model.smtp.from_name' => 'required', 'model.smtp_from_name' => 'required',
'model.smtp.recipients' => 'nullable', 'model.smtp_recipients' => 'nullable',
'model.smtp.host' => 'required', 'model.smtp_host' => 'required',
'model.smtp.port' => 'required', 'model.smtp_port' => 'required',
'model.smtp.encryption' => 'nullable', 'model.smtp_encryption' => 'nullable',
'model.smtp.username' => 'nullable', 'model.smtp_username' => 'nullable',
'model.smtp.password' => 'nullable', 'model.smtp_password' => 'nullable',
'model.smtp.timeout' => 'nullable', 'model.smtp_timeout' => 'nullable',
'model.smtp.test_recipients' => 'nullable', 'model.smtp_notifications_test' => 'nullable|boolean',
'model.smtp_notifications.test' => 'nullable|boolean', 'model.smtp_notifications_deployments' => 'nullable|boolean',
'model.smtp_notifications.deployments' => 'nullable|boolean', 'model.smtp_notifications_status_changes' => 'nullable|boolean',
'model.discord_notifications.test' => 'nullable|boolean', 'model.smtp_notifications_database_backups' => 'nullable|boolean',
'model.discord_notifications.deployments' => 'nullable|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'model.smtp.from_address' => 'From Address', 'model.smtp_from_address' => 'From Address',
'model.smtp.from_name' => 'From Name', 'model.smtp_from_name' => 'From Name',
'model.smtp.recipients' => 'Recipients', 'model.smtp_recipients' => 'Recipients',
'model.smtp.host' => 'Host', 'model.smtp_host' => 'Host',
'model.smtp.port' => 'Port', 'model.smtp_port' => 'Port',
'model.smtp.encryption' => 'Encryption', 'model.smtp_encryption' => 'Encryption',
'model.smtp.username' => 'Username', 'model.smtp_username' => 'Username',
'model.smtp.password' => 'Password', 'model.smtp_password' => 'Password',
'model.smtp.test_recipients' => 'Test Recipients',
]; ];
public function mount()
{
$this->decrypt();
$this->emails = auth()->user()->email;
}
private function decrypt() private function decrypt()
{ {
if (data_get($this->model, 'smtp.password')) { if (data_get($this->model, 'smtp_password')) {
try { try {
$this->model->smtp->password = decrypt($this->model->smtp->password); $this->model->smtp_password = decrypt($this->model->smtp_password);
} catch (\Exception $e) { } catch (\Exception $e) {
} }
} }
} }
public function mount()
{
$this->decrypt();
}
public function copyFromInstanceSettings() public function copyFromInstanceSettings()
{ {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
if ($settings->smtp->enabled) { if ($settings->smtp_enabled) {
$this->model->smtp->enabled = true; $team = auth()->user()->currentTeam();
$this->model->smtp->from_address = $settings->smtp->from_address; $team->update([
$this->model->smtp->from_name = $settings->smtp->from_name; 'smtp_enabled' => $settings->smtp_enabled,
$this->model->smtp->recipients = $settings->smtp->recipients; 'smtp_from_address' => $settings->smtp_from_address,
$this->model->smtp->host = $settings->smtp->host; 'smtp_from_name' => $settings->smtp_from_name,
$this->model->smtp->port = $settings->smtp->port; 'smtp_recipients' => $settings->smtp_recipients,
$this->model->smtp->encryption = $settings->smtp->encryption; 'smtp_host' => $settings->smtp_host,
$this->model->smtp->username = $settings->smtp->username; 'smtp_port' => $settings->smtp_port,
$this->model->smtp->password = $settings->smtp->password; 'smtp_encryption' => $settings->smtp_encryption,
$this->model->smtp->timeout = $settings->smtp->timeout; 'smtp_username' => $settings->smtp_username,
$this->model->smtp->test_recipients = $settings->smtp->test_recipients; 'smtp_password' => $settings->smtp_password,
$this->saveModel(); '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 { } else {
$this->emit('error', 'Instance SMTP settings are not enabled.'); $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() public function submit()
{ {
$this->resetErrorBag(); $this->resetErrorBag();
$this->validate(); $this->validate();
if ($this->model->smtp->password) { if ($this->model->smtp_password) {
$this->model->smtp->password = encrypt($this->model->smtp->password); $this->model->smtp_password = encrypt($this->model->smtp_password);
} else { } else {
$this->model->smtp->password = null; $this->model->smtp_password = null;
} }
$this->model->smtp->recipients = str_replace(' ', '', $this->model->smtp->recipients); $this->model->smtp_recipients = str_replace(' ', '', $this->model->smtp_recipients);
$this->model->smtp->test_recipients = str_replace(' ', '', $this->model->smtp->test_recipients);
$this->saveModel(); $this->saveModel();
} }
public function saveModel() public function saveModel()
{ {
$this->model->save(); $this->model->save();
@ -96,18 +123,4 @@ public function saveModel()
} }
$this->emit('success', 'Settings saved.'); $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.description' => 'description',
'private_key.private_key' => 'private key' 'private_key.private_key' => 'private key'
]; ];
public function delete() public function delete()
{ {
try { try {
if ($this->private_key->isEmpty()) { if ($this->private_key->isEmpty()) {
$this->private_key->delete(); $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'); 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.'); $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); return general_error_handler(err: $e, that: $this);
} }
} }
public function changePrivateKey() public function changePrivateKey()
{ {
try { try {
@ -41,7 +43,7 @@ public function changePrivateKey()
$this->private_key->private_key .= "\n"; $this->private_key->private_key .= "\n";
} }
$this->private_key->save(); $this->private_key->save();
session('currentTeam')->privateKeys = PrivateKey::where('team_id', session('currentTeam')->id)->get(); refreshPrivateKey($this->private_key);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler(err: $e, that: $this); return general_error_handler(err: $e, that: $this);
} }

View File

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

View File

@ -17,12 +17,14 @@ class Form extends Component
protected $validationAttributes = [ protected $validationAttributes = [
'name' => 'name', 'name' => 'name',
]; ];
public function mount() public function mount()
{ {
$this->userId = auth()->user()->id; $this->userId = auth()->user()->id;
$this->name = auth()->user()->name; $this->name = auth()->user()->name;
$this->email = auth()->user()->email; $this->email = auth()->user()->email;
} }
public function submit() 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 ApplicationDeploymentQueue $application_deployment_queue;
public $isKeepAliveOn = true; public $isKeepAliveOn = true;
protected $listeners = ['refreshQueue']; protected $listeners = ['refreshQueue'];
public function refreshQueue() public function refreshQueue()
{ {
$this->application_deployment_queue->refresh(); $this->application_deployment_queue->refresh();
} }
public function polling() public function polling()
{ {
$this->emit('deploymentFinished'); $this->emit('deploymentFinished');

View File

@ -8,17 +8,16 @@
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Process;
use Livewire\Component;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Livewire\Component;
class DeploymentNavbar extends Component class DeploymentNavbar extends Component
{ {
protected $listeners = ['deploymentFinished'];
public ApplicationDeploymentQueue $application_deployment_queue; public ApplicationDeploymentQueue $application_deployment_queue;
public Application $application; public Application $application;
public Server $server; public Server $server;
public bool $is_debug_enabled = false; public bool $is_debug_enabled = false;
protected $listeners = ['deploymentFinished'];
public function mount() public function mount()
{ {
@ -26,10 +25,12 @@ public function mount()
$this->server = $this->application->destination->server; $this->server = $this->application->destination->server;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->is_debug_enabled = $this->application->settings->is_debug_enabled;
} }
public function deploymentFinished() public function deploymentFinished()
{ {
$this->application_deployment_queue->refresh(); $this->application_deployment_queue->refresh();
} }
public function show_debug() public function show_debug()
{ {
$this->application->settings->is_debug_enabled = !$this->application->settings->is_debug_enabled; $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->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->emit('refreshQueue'); $this->emit('refreshQueue');
} }
public function cancel() public function cancel()
{ {
try { try {
@ -66,4 +68,4 @@ public function cancel()
return general_error_handler(err: $e, that: $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -21,16 +21,19 @@ class Source extends Component
'application.git_branch' => 'branch', 'application.git_branch' => 'branch',
'application.git_commit_sha' => 'commit sha', '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() public function mount()
{ {
$this->get_private_keys(); $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) public function setPrivateKey(int $private_key_id)
{ {
$this->application->private_key_id = $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->application->refresh();
$this->get_private_keys(); $this->get_private_keys();
} }
public function submit() public function submit()
{ {
$this->validate(); $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() public function mount()
{ {
$this->parameters = getRouteParameters(); $this->parameters = get_route_parameters();
} }
public function delete() public function delete()
{ {
$this->validate([ $this->validate([

View File

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

View File

@ -11,7 +11,7 @@ public function createEmptyProject()
{ {
$project = Project::create([ $project = Project::create([
'name' => generate_random_name(), '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']); 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\Application;
use App\Models\GithubApp; use App\Models\GithubApp;
use App\Models\Project; use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use App\Models\SwarmDocker; use App\Models\SwarmDocker;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Livewire\Component; use Livewire\Component;
class GithubPrivateRepository extends Component class GithubPrivateRepository extends Component
{ {
public $current_step = 'github_apps';
public $github_apps; public $github_apps;
public GithubApp $github_app; public GithubApp $github_app;
public $parameters; public $parameters;
@ -29,52 +27,23 @@ class GithubPrivateRepository extends Component
public string $selected_branch_name = 'main'; public string $selected_branch_name = 'main';
public string $token; public string $token;
protected int $page = 1;
public $repositories; public $repositories;
public int $total_repositories_count = 0; public int $total_repositories_count = 0;
public $branches; public $branches;
public int $total_branches_count = 0; public int $total_branches_count = 0;
public int $port = 3000; public int $port = 3000;
public bool $is_static = false; public bool $is_static = false;
public string|null $publish_directory = null; public string|null $publish_directory = null;
protected int $page = 1;
public function mount() public function mount()
{ {
$this->parameters = getRouteParameters(); $this->parameters = get_route_parameters();
$this->query = request()->query(); $this->query = request()->query();
$this->repositories = $this->branches = collect(); $this->repositories = $this->branches = collect();
$this->github_apps = GithubApp::private(); $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) public function loadRepositories($github_app_id)
{ {
$this->repositories = collect(); $this->repositories = collect();
@ -90,7 +59,24 @@ public function loadRepositories($github_app_id)
} }
} }
$this->selected_repository_id = $this->repositories[0]['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() public function loadBranches()
{ {
$this->selected_repository_owner = $this->repositories->where('id', $this->selected_repository_id)->first()['owner']['login']; $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() public function submit()
{ {
try { try {
@ -134,7 +134,7 @@ public function submit()
'destination_id' => $destination->id, 'destination_id' => $destination->id,
'destination_type' => $destination_class, 'destination_type' => $destination_class,
'source_id' => $this->github_app->id, 'source_id' => $this->github_app->id,
'source_type' => $this->github_app->getMorphClass() 'source_type' => $this->github_app->getMorphClass()
]); ]);
$application->settings->is_static = $this->is_static; $application->settings->is_static = $this->is_static;
$application->settings->save(); $application->settings->save();
@ -148,6 +148,7 @@ public function submit()
return general_error_handler(err: $e, that: $this); return general_error_handler(err: $e, that: $this);
} }
} }
public function instantSave() public function instantSave()
{ {
if ($this->is_static) { if ($this->is_static) {

View File

@ -14,6 +14,7 @@
class GithubPrivateRepositoryDeployKey extends Component class GithubPrivateRepositoryDeployKey extends Component
{ {
public $current_step = 'private_keys';
public $parameters; public $parameters;
public $query; public $query;
public $private_keys; public $private_keys;
@ -26,14 +27,7 @@ class GithubPrivateRepositoryDeployKey extends Component
public null|string $publish_directory = null; public null|string $publish_directory = null;
public string $repository_url; public string $repository_url;
private object $repository_url_parsed;
public string $branch; public string $branch;
private GithubApp|GitlabApp $git_source;
private string $git_host;
private string $git_repository;
private string $git_branch;
protected $rules = [ protected $rules = [
'repository_url' => 'required|url', 'repository_url' => 'required|url',
'branch' => 'required|string', 'branch' => 'required|string',
@ -48,15 +42,22 @@ class GithubPrivateRepositoryDeployKey extends Component
'is_static' => 'Is static', 'is_static' => 'Is static',
'publish_directory' => 'Publish directory', '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() public function mount()
{ {
if (isDev()) { if (is_dev()) {
$this->repository_url = 'https://github.com/coollabsio/coolify-examples'; $this->repository_url = 'https://github.com/coollabsio/coolify-examples';
} }
$this->parameters = getRouteParameters(); $this->parameters = get_route_parameters();
$this->query = request()->query(); $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() public function instantSave()
{ {
if ($this->is_static) { if ($this->is_static) {
@ -67,29 +68,13 @@ public function instantSave()
$this->publish_directory = null; $this->publish_directory = null;
} }
} }
public function setPrivateKey($private_key_id) public function setPrivateKey($private_key_id)
{ {
$this->private_key_id = $private_key_id; $this->private_key_id = $private_key_id;
$this->current_step = 'repository';
} }
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
}
}
public function submit() public function submit()
{ {
$this->validate(); $this->validate();
@ -121,7 +106,7 @@ public function submit()
'destination_type' => $destination_class, 'destination_type' => $destination_class,
'private_key_id' => $this->private_key_id, 'private_key_id' => $this->private_key_id,
'source_id' => $this->git_source->id, 'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass() 'source_type' => $this->git_source->getMorphClass()
]; ];
$application = Application::create($application_init); $application = Application::create($application_init);
$application->settings->is_static = $this->is_static; $application->settings->is_static = $this->is_static;
@ -136,4 +121,24 @@ public function submit()
return general_error_handler(err: $e, that: $this); 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 class PublicGitRepository extends Component
{ {
public string $repository_url; public string $repository_url;
private object $repository_url_parsed;
public int $port = 3000; public int $port = 3000;
public string $type; public string $type;
public $parameters; public $parameters;
public $query; public $query;
public bool $branch_found = false; public bool $branch_found = false;
public string $selected_branch = 'main'; public string $selected_branch = 'main';
public bool $is_static = false; public bool $is_static = false;
@ -29,11 +26,6 @@ class PublicGitRepository extends Component
public string $git_branch = 'main'; public string $git_branch = 'main';
public int $rate_limit_remaining = 0; public int $rate_limit_remaining = 0;
public $rate_limit_reset = 0; public $rate_limit_reset = 0;
private GithubApp|GitlabApp $git_source;
private string $git_host;
private string $git_repository;
protected $rules = [ protected $rules = [
'repository_url' => 'required|url', 'repository_url' => 'required|url',
'port' => 'required|numeric', 'port' => 'required|numeric',
@ -46,13 +38,18 @@ class PublicGitRepository extends Component
'is_static' => 'static', 'is_static' => 'static',
'publish_directory' => 'publish directory', '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() public function mount()
{ {
if (isDev()) { if (is_dev()) {
$this->repository_url = 'https://github.com/coollabsio/coolify-examples'; $this->repository_url = 'https://github.com/coollabsio/coolify-examples';
$this->port = 3000; $this->port = 3000;
} }
$this->parameters = getRouteParameters(); $this->parameters = get_route_parameters();
$this->query = request()->query(); $this->query = request()->query();
} }
@ -67,12 +64,7 @@ public function instantSave()
} }
$this->emit('success', 'Application settings updated!'); $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() public function load_branch()
{ {
$this->branch_found = false; $this->branch_found = false;
@ -82,7 +74,9 @@ public function load_branch()
$this->get_git_source(); $this->get_git_source();
try { try {
$this->get_branch(); $this->get_branch();
$this->selected_branch = $this->git_branch;
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
} }
if (!$this->branch_found && $this->git_branch == 'main') { if (!$this->branch_found && $this->git_branch == 'main') {
@ -94,6 +88,7 @@ public function load_branch()
} }
} }
} }
private function get_git_source() private function get_git_source()
{ {
$this->repository_url_parsed = Url::fromString($this->repository_url); $this->repository_url_parsed = Url::fromString($this->repository_url);
@ -109,6 +104,14 @@ private function get_git_source()
// Not supported yet // 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() public function submit()
{ {
try { 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 <?php
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Shared;
use App\Models\Application;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
class Danger extends Component class Danger extends Component
{ {
public Application $application; public $resource;
public array $parameters; public array $parameters;
public string|null $modalId = null; public string|null $modalId = null;
public function mount() public function mount()
{ {
$this->modalId = new Cuid2(7); $this->modalId = new Cuid2(7);
$this->parameters = getRouteParameters(); $this->parameters = get_route_parameters();
} }
public function delete() 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); instant_remote_process(["docker rm -f {$this->resource->uuid}"], $destination->server);
$this->application->delete(); $this->resource->delete();
return redirect()->route('project.resources', [ return redirect()->route('project.resources', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name'] 'environment_name' => $this->parameters['environment_name']

View File

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

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application\EnvironmentVariable; namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use Livewire\Component; use Livewire\Component;
@ -23,10 +23,12 @@ class Add extends Component
'value' => 'value', 'value' => 'value',
'is_build_time' => 'build', 'is_build_time' => 'build',
]; ];
public function mount() public function mount()
{ {
$this->parameters = getRouteParameters(); $this->parameters = get_route_parameters();
} }
public function submit() public function submit()
{ {
ray('submitting'); ray('submitting');
@ -39,6 +41,7 @@ public function submit()
]); ]);
$this->clear(); $this->clear();
} }
public function clear() public function clear()
{ {
$this->key = ''; $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 <?php
namespace App\Http\Livewire\Project\Application\EnvironmentVariable; namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Livewire\Component; use Livewire\Component;
@ -21,10 +21,16 @@ class Show extends Component
'value' => 'value', 'value' => 'value',
'is_build_time' => 'build', 'is_build_time' => 'build',
]; ];
public function mount() public function mount()
{ {
$this->modalId = new Cuid2(7); $this->modalId = new Cuid2(7);
$this->parameters = getRouteParameters(); $this->parameters = get_route_parameters();
}
public function instantSave()
{
$this->submit();
} }
public function submit() public function submit()
{ {
@ -32,6 +38,7 @@ public function submit()
$this->env->save(); $this->env->save();
$this->emit('success', 'Environment variable updated successfully.'); $this->emit('success', 'Environment variable updated successfully.');
} }
public function delete() public function delete()
{ {
$this->env->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 <?php
namespace App\Http\Livewire\Project\Application\Storages; namespace App\Http\Livewire\Project\Shared\Storages;
use Livewire\Component; use Livewire\Component;
@ -22,10 +22,12 @@ class Add extends Component
'mount_path' => 'mount', 'mount_path' => 'mount',
'host_path' => 'host', 'host_path' => 'host',
]; ];
public function mount() public function mount()
{ {
$this->parameters = getRouteParameters(); $this->parameters = get_route_parameters();
} }
public function submit() public function submit()
{ {
$this->validate(); $this->validate();
@ -35,6 +37,7 @@ public function submit()
'host_path' => $this->host_path, 'host_path' => $this->host_path,
]); ]);
} }
public function clear() public function clear()
{ {
$this->name = ''; $this->name = '';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@
namespace App\Http\Livewire\Server; namespace App\Http\Livewire\Server;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Facades\Storage;
use Livewire\Component; use Livewire\Component;
use Masmerise\Toaster\Toaster; use Masmerise\Toaster\Toaster;
@ -13,35 +12,33 @@ class PrivateKey extends Component
public $privateKeys; public $privateKeys;
public $parameters; 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) public function setPrivateKey($private_key_id)
{ {
$this->server->update([ $this->server->update([
'private_key_id' => $private_key_id 'private_key_id' => $private_key_id
]); ]);
refreshPrivateKey($this->server->privateKey);
// Delete the old ssh mux file to force a new one to be created
Storage::disk('ssh-mux')->delete($this->server->muxFilename());
$this->server->refresh(); $this->server->refresh();
$this->checkConnection(); $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() public function mount()
{ {
$this->parameters = getRouteParameters(); $this->parameters = get_route_parameters();
} }
} }

View File

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

View File

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

View File

@ -9,20 +9,13 @@
class Status extends Component class Status extends Component
{ {
public Server $server; public Server $server;
protected $listeners = ['proxyStatusUpdated'];
public function proxyStatusUpdated() public function get_status()
{ {
dispatch_sync(new ProxyContainerStatusJob(
server: $this->server
));
$this->server->refresh(); $this->server->refresh();
} $this->emit('proxyStatusUpdated');
public function proxyStatus()
{
try {
dispatch_sync(new ProxyContainerStatusJob(
server: $this->server
));
$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_min' => 'Public port min',
'settings.public_port_max' => 'Public port max', 'settings.public_port_max' => 'Public port max',
]; ];
public function mount() public function mount()
{ {
$this->do_not_track = $this->settings->do_not_track; $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->is_registration_enabled = $this->settings->is_registration_enabled;
$this->next_channel = $this->settings->next_channel; $this->next_channel = $this->settings->next_channel;
} }
public function instantSave() public function instantSave()
{ {
$this->settings->do_not_track = $this->do_not_track; $this->settings->do_not_track = $this->do_not_track;
@ -47,6 +49,21 @@ public function instantSave()
$this->settings->save(); $this->settings->save();
$this->emit('success', 'Settings updated!'); $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() private function setup_instance_fqdn()
{ {
$file = "$this->dynamic_config_path/coolify.yaml"; $file = "$this->dynamic_config_path/coolify.yaml";
@ -110,6 +127,7 @@ private function setup_instance_fqdn()
dispatch(new ProxyStartJob($this->server)); dispatch(new ProxyStartJob($this->server));
} }
} }
private function save_configuration_to_disk(array $traefik_dynamic_conf, string $file) private function save_configuration_to_disk(array $traefik_dynamic_conf, string $file)
{ {
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2); $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); 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; namespace App\Http\Livewire\Settings;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Notifications\TransactionalEmails\TestEmail; use App\Notifications\TransactionalEmails\Test;
use Illuminate\Support\Facades\Notification;
use Livewire\Component; use Livewire\Component;
class Email extends Component class Email extends Component
{ {
public InstanceSettings $settings; public InstanceSettings $settings;
public string $emails;
protected $rules = [ protected $rules = [
'settings.smtp.enabled' => 'nullable|boolean', 'settings.smtp_enabled' => 'nullable|boolean',
'settings.smtp.host' => 'required', 'settings.smtp_host' => 'required',
'settings.smtp.port' => 'required|numeric', 'settings.smtp_port' => 'required|numeric',
'settings.smtp.encryption' => 'nullable', 'settings.smtp_encryption' => 'nullable',
'settings.smtp.username' => 'nullable', 'settings.smtp_username' => 'nullable',
'settings.smtp.password' => 'nullable', 'settings.smtp_password' => 'nullable',
'settings.smtp.timeout' => 'nullable', 'settings.smtp_timeout' => 'nullable',
'settings.smtp.test_recipients' => 'nullable', 'settings.smtp_from_address' => 'required|email',
'settings.smtp.from_address' => 'required|email', 'settings.smtp_from_name' => 'required',
'settings.smtp.from_name' => 'required',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'settings.smtp.from_address' => 'From Address', 'settings.smtp_from_address' => 'From Address',
'settings.smtp.from_name' => 'From Name', 'settings.smtp_from_name' => 'From Name',
'settings.smtp.recipients' => 'Recipients', 'settings.smtp_recipients' => 'Recipients',
'settings.smtp.host' => 'Host', 'settings.smtp_host' => 'Host',
'settings.smtp.port' => 'Port', 'settings.smtp_port' => 'Port',
'settings.smtp.encryption' => 'Encryption', 'settings.smtp_encryption' => 'Encryption',
'settings.smtp.username' => 'Username', 'settings.smtp_username' => 'Username',
'settings.smtp.password' => 'Password', 'settings.smtp_password' => 'Password',
'settings.smtp.test_recipients' => 'Test Recipients',
]; ];
public function mount() public function mount()
{ {
$this->decrypt(); $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() public function instantSave()
{ {
try { try {
$this->submit(); $this->submit();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->settings->smtp->enabled = false; $this->settings->smtp_enabled = false;
$this->validate(); $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() public function submit()
{ {
$this->resetErrorBag(); $this->resetErrorBag();
$this->validate(); $this->validate();
if ($this->settings->smtp->password) { if ($this->settings->smtp_password) {
$this->settings->smtp->password = encrypt($this->settings->smtp->password); $this->settings->smtp_password = encrypt($this->settings->smtp_password);
} else { } 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->settings->save();
$this->emit('success', 'Transaction email settings updated successfully.'); $this->emit('success', 'Transaction email settings updated successfully.');
$this->decrypt(); $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.webhook_secret' => 'nullable',
'github_app.is_system_wide' => 'required|bool', 'github_app.is_system_wide' => 'required|bool',
]; ];
public function mount() public function mount()
{ {
$this->webhook_endpoint = $this->ipv4; $this->webhook_endpoint = $this->ipv4;
$this->parameters = getRouteParameters(); $this->parameters = get_route_parameters();
$this->is_system_wide = $this->github_app->is_system_wide; $this->is_system_wide = $this->github_app->is_system_wide;
} }
public function submit() public function submit()
{ {
try { try {
@ -49,6 +51,7 @@ public function submit()
return general_error_handler(err: $e, that: $this); return general_error_handler(err: $e, that: $this);
} }
} }
public function instantSave() public function instantSave()
{ {
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,13 +9,15 @@ class Invitations extends Component
{ {
public $invitations; public $invitations;
protected $listeners = ['refreshInvitations']; protected $listeners = ['refreshInvitations'];
public function refreshInvitations()
{
$this->invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get();
}
public function deleteInvitation(int $invitation_id) public function deleteInvitation(int $invitation_id)
{ {
TeamInvitation::find($invitation_id)->delete(); TeamInvitation::find($invitation_id)->delete();
$this->refreshInvitations(); $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\TeamInvitation;
use App\Models\User; use App\Models\User;
use App\Notifications\TransactionalEmails\InvitationLinkEmail; use App\Notifications\TransactionalEmails\InvitationLink;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
@ -12,14 +12,17 @@ class InviteLink extends Component
{ {
public string $email; public string $email;
public string $role = 'member'; public string $role = 'member';
public function mount() public function mount()
{ {
$this->email = isDev() ? 'test3@example.com' : ''; $this->email = is_dev() ? 'test3@example.com' : '';
} }
public function viaEmail() public function viaEmail()
{ {
$this->generate_invite_link(isEmail: true); $this->generate_invite_link(isEmail: true);
} }
private function generate_invite_link(bool $isEmail = false) private function generate_invite_link(bool $isEmail = false)
{ {
try { 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)."); 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)) { 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); $invitation = TeamInvitation::whereEmail($this->email);
@ -50,7 +53,7 @@ private function generate_invite_link(bool $isEmail = false)
} }
TeamInvitation::firstOrCreate([ TeamInvitation::firstOrCreate([
'team_id' => session('currentTeam')->id, 'team_id' => auth()->user()->currentTeam()->id,
'uuid' => $uuid, 'uuid' => $uuid,
'email' => $this->email, 'email' => $this->email,
'role' => $this->role, 'role' => $this->role,
@ -58,7 +61,7 @@ private function generate_invite_link(bool $isEmail = false)
'via' => $isEmail ? 'email' : 'link', 'via' => $isEmail ? 'email' : 'link',
]); ]);
if ($isEmail) { if ($isEmail) {
$user->first()->notify(new InvitationLinkEmail()); $user->first()->notify(new InvitationLink);
$this->emit('success', 'Invitation sent via email successfully.'); $this->emit('success', 'Invitation sent via email successfully.');
} else { } else {
$this->emit('success', 'Invitation link generated.'); $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); return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message);
} }
} }
public function viaLink() public function viaLink()
{ {
$this->generate_invite_link(); $this->generate_invite_link();

View File

@ -8,19 +8,22 @@
class Member extends Component class Member extends Component
{ {
public User $member; public User $member;
public function makeAdmin() 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'); $this->emit('reloadWindow');
} }
public function makeReadonly() 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'); $this->emit('reloadWindow');
} }
public function remove() public function remove()
{ {
$this->member->teams()->detach(session('currentTeam')); $this->member->teams()->detach(auth()->user()->currentTeam());
$this->emit('reloadWindow'); $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);
}
}
}

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