Merge branch 'next' into fix-install-scirpt-root-and-storage
This commit is contained in:
@@ -11,7 +11,7 @@ on:
|
|||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
- docker/coolify-realtime/Dockerfile
|
- docker/coolify-realtime/Dockerfile
|
||||||
- docker/testing-host/Dockerfile
|
- docker/testing-host/Dockerfile
|
||||||
- templates/*
|
- templates/**
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_REGISTRY: ghcr.io
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
2
.github/workflows/coolify-staging-build.yml
vendored
2
.github/workflows/coolify-staging-build.yml
vendored
@@ -11,7 +11,7 @@ on:
|
|||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
- docker/coolify-realtime/Dockerfile
|
- docker/coolify-realtime/Dockerfile
|
||||||
- docker/testing-host/Dockerfile
|
- docker/testing-host/Dockerfile
|
||||||
- templates/*
|
- templates/**
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_REGISTRY: ghcr.io
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
37
app/Actions/Application/IsHorizonQueueEmpty.php
Normal file
37
app/Actions/Application/IsHorizonQueueEmpty.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use Laravel\Horizon\Contracts\JobRepository;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class IsHorizonQueueEmpty
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$hostname = gethostname();
|
||||||
|
$recent = app(JobRepository::class)->getRecent();
|
||||||
|
if ($recent) {
|
||||||
|
$running = $recent->filter(function ($job) use ($hostname) {
|
||||||
|
$payload = json_decode($job->payload);
|
||||||
|
$tags = data_get($payload, 'tags');
|
||||||
|
|
||||||
|
return $job->status != 'completed' &&
|
||||||
|
$job->status != 'failed' &&
|
||||||
|
isset($tags) &&
|
||||||
|
is_array($tags) &&
|
||||||
|
in_array('server:'.$hostname, $tags);
|
||||||
|
});
|
||||||
|
if ($running->count() > 0) {
|
||||||
|
echo 'false';
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo 'true';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@@ -12,7 +12,7 @@ class InstallDocker
|
|||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
$dockerVersion = config('constants.docker_install_version');
|
$dockerVersion = config('constants.docker.minimum_required_version');
|
||||||
$supported_os_type = $server->validateOS();
|
$supported_os_type = $server->validateOS();
|
||||||
if (! $supported_os_type) {
|
if (! $supported_os_type) {
|
||||||
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.');
|
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.');
|
||||||
|
49
app/Console/Commands/CloudCheckSubscription.php
Normal file
49
app/Console/Commands/CloudCheckSubscription.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CloudCheckSubscription extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'cloud:check-subscription';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Check Cloud subscriptions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
$activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->get();
|
||||||
|
foreach ($activeSubscribers as $team) {
|
||||||
|
$stripeSubscriptionId = $team->subscription->stripe_subscription_id;
|
||||||
|
$stripeInvoicePaid = $team->subscription->stripe_invoice_paid;
|
||||||
|
$stripeCustomerId = $team->subscription->stripe_customer_id;
|
||||||
|
if (! $stripeSubscriptionId) {
|
||||||
|
echo "Team {$team->id} has no subscription, but invoice status is: {$stripeInvoicePaid}\n";
|
||||||
|
echo "Link on Stripe: https://dashboard.stripe.com/customers/{$stripeCustomerId}\n";
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$subscription = $stripe->subscriptions->retrieve($stripeSubscriptionId);
|
||||||
|
if ($subscription->status === 'active') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
echo "Subscription {$stripeSubscriptionId} is not active ({$subscription->status})\n";
|
||||||
|
echo "Link on Stripe: https://dashboard.stripe.com/subscriptions/{$stripeSubscriptionId}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -6,6 +6,7 @@ use App\Models\InstanceSettings;
|
|||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class Dev extends Command
|
class Dev extends Command
|
||||||
{
|
{
|
||||||
@@ -37,6 +38,11 @@ class Dev extends Command
|
|||||||
$error = preg_replace('/^\h*\v+/m', '', $error);
|
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||||
echo $error;
|
echo $error;
|
||||||
echo $process->output();
|
echo $process->output();
|
||||||
|
// Convert YAML to JSON
|
||||||
|
$yaml = file_get_contents('openapi.yaml');
|
||||||
|
$json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT);
|
||||||
|
file_put_contents('openapi.json', $json);
|
||||||
|
echo "Converted OpenAPI YAML to JSON.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function init()
|
public function init()
|
||||||
|
@@ -57,12 +57,19 @@ class Init extends Command
|
|||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
|
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
try {
|
||||||
if ($response->successful()) {
|
$this->pullTemplatesFromCDN();
|
||||||
$services = $response->json();
|
} catch (\Throwable $e) {
|
||||||
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
echo "Could not pull templates from CDN: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isCloud()) {
|
||||||
|
try {
|
||||||
|
$this->pullTemplatesFromCDN();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Could not pull templates from CDN: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
$localhost = $this->servers->where('id', 0)->first();
|
$localhost = $this->servers->where('id', 0)->first();
|
||||||
$localhost->setupDynamicProxyConfiguration();
|
$localhost->setupDynamicProxyConfiguration();
|
||||||
@@ -80,6 +87,14 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function pullTemplatesFromCDN()
|
||||||
|
{
|
||||||
|
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||||
|
if ($response->successful()) {
|
||||||
|
$services = $response->json();
|
||||||
|
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||||
|
}
|
||||||
|
}
|
||||||
// private function disable_metrics()
|
// private function disable_metrics()
|
||||||
// {
|
// {
|
||||||
// if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
// if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||||
|
@@ -28,6 +28,8 @@ class Kernel extends ConsoleKernel
|
|||||||
{
|
{
|
||||||
private $allServers;
|
private $allServers;
|
||||||
|
|
||||||
|
private Schedule $scheduleInstance;
|
||||||
|
|
||||||
private InstanceSettings $settings;
|
private InstanceSettings $settings;
|
||||||
|
|
||||||
private string $updateCheckFrequency;
|
private string $updateCheckFrequency;
|
||||||
@@ -36,82 +38,90 @@ class Kernel extends ConsoleKernel
|
|||||||
|
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
|
$this->scheduleInstance = $schedule;
|
||||||
$this->allServers = Server::where('ip', '!=', '1.2.3.4');
|
$this->allServers = Server::where('ip', '!=', '1.2.3.4');
|
||||||
|
|
||||||
$this->settings = instanceSettings();
|
$this->settings = instanceSettings();
|
||||||
$this->updateCheckFrequency = $this->settings->update_check_frequency ?: '0 * * * *';
|
$this->updateCheckFrequency = $this->settings->update_check_frequency ?: '0 * * * *';
|
||||||
|
|
||||||
$this->instanceTimezone = $this->settings->instance_timezone ?: config('app.timezone');
|
$this->instanceTimezone = $this->settings->instance_timezone ?: config('app.timezone');
|
||||||
|
|
||||||
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
|
if (validate_timezone($this->instanceTimezone) === false) {
|
||||||
|
$this->instanceTimezone = config('app.timezone');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||||
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyMinute();
|
$this->scheduleInstance->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$this->scheduleInstance->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
$schedule->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer();
|
$this->scheduleInstance->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer();
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->checkResources($schedule);
|
$this->checkResources();
|
||||||
|
|
||||||
$this->checkScheduledBackups($schedule);
|
$this->checkScheduledBackups();
|
||||||
$this->checkScheduledTasks($schedule);
|
$this->checkScheduledTasks();
|
||||||
|
|
||||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
$this->scheduleInstance->command('uploads:clear')->everyTwoMinutes();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$this->scheduleInstance->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
|
$this->scheduleInstance->command('cleanup:unreachable-servers')->daily()->onOneServer();
|
||||||
$schedule->job(new PullTemplatesFromCDN)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$this->scheduleInstance->job(new PullTemplatesFromCDN)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
$this->scheduleUpdates($schedule);
|
|
||||||
|
$this->scheduleInstance->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
|
$this->scheduleUpdates();
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->checkResources($schedule);
|
$this->checkResources();
|
||||||
|
|
||||||
$this->pullImages($schedule);
|
$this->pullImages();
|
||||||
|
|
||||||
$this->checkScheduledBackups($schedule);
|
$this->checkScheduledBackups();
|
||||||
$this->checkScheduledTasks($schedule);
|
$this->checkScheduledTasks();
|
||||||
|
|
||||||
$schedule->command('cleanup:database --yes')->daily();
|
$this->scheduleInstance->command('cleanup:database --yes')->daily();
|
||||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
$this->scheduleInstance->command('uploads:clear')->everyTwoMinutes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function pullImages($schedule): void
|
private function pullImages(): void
|
||||||
{
|
{
|
||||||
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
|
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
if ($server->isSentinelEnabled()) {
|
if ($server->isSentinelEnabled()) {
|
||||||
$schedule->job(function () use ($server) {
|
$this->scheduleInstance->job(function () use ($server) {
|
||||||
CheckAndStartSentinelJob::dispatch($server);
|
CheckAndStartSentinelJob::dispatch($server);
|
||||||
})->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
})->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$schedule->job(new CheckHelperImageJob)
|
$this->scheduleInstance->job(new CheckHelperImageJob)
|
||||||
->cron($this->updateCheckFrequency)
|
->cron($this->updateCheckFrequency)
|
||||||
->timezone($this->instanceTimezone)
|
->timezone($this->instanceTimezone)
|
||||||
->onOneServer();
|
->onOneServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function scheduleUpdates($schedule): void
|
private function scheduleUpdates(): void
|
||||||
{
|
{
|
||||||
$schedule->job(new CheckForUpdatesJob)
|
$this->scheduleInstance->job(new CheckForUpdatesJob)
|
||||||
->cron($this->updateCheckFrequency)
|
->cron($this->updateCheckFrequency)
|
||||||
->timezone($this->instanceTimezone)
|
->timezone($this->instanceTimezone)
|
||||||
->onOneServer();
|
->onOneServer();
|
||||||
|
|
||||||
if ($this->settings->is_auto_update_enabled) {
|
if ($this->settings->is_auto_update_enabled) {
|
||||||
$autoUpdateFrequency = $this->settings->auto_update_frequency;
|
$autoUpdateFrequency = $this->settings->auto_update_frequency;
|
||||||
$schedule->job(new UpdateCoolifyJob)
|
$this->scheduleInstance->job(new UpdateCoolifyJob)
|
||||||
->cron($autoUpdateFrequency)
|
->cron($autoUpdateFrequency)
|
||||||
->timezone($this->instanceTimezone)
|
->timezone($this->instanceTimezone)
|
||||||
->onOneServer();
|
->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkResources($schedule): void
|
private function checkResources(): void
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
$servers = $this->allServers->whereHas('team.subscription')->get();
|
$servers = $this->allServers->whereHas('team.subscription')->get();
|
||||||
@@ -128,31 +138,34 @@ class Kernel extends ConsoleKernel
|
|||||||
$lastSentinelUpdate = $server->sentinel_updated_at;
|
$lastSentinelUpdate = $server->sentinel_updated_at;
|
||||||
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
||||||
// Check container status every minute if Sentinel does not activated
|
// Check container status every minute if Sentinel does not activated
|
||||||
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
if (validate_timezone($serverTimezone) === false) {
|
||||||
// $schedule->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
|
$serverTimezone = config('app.timezone');
|
||||||
|
}
|
||||||
|
$this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer();
|
||||||
|
// $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
|
||||||
|
|
||||||
// Check storage usage every 10 minutes if Sentinel does not activated
|
// Check storage usage every 10 minutes if Sentinel does not activated
|
||||||
$schedule->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
$this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
if ($server->settings->force_docker_cleanup) {
|
if ($server->settings->force_docker_cleanup) {
|
||||||
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
$this->scheduleInstance->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
} else {
|
} else {
|
||||||
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
$this->scheduleInstance->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup multiplexed connections every hour
|
// Cleanup multiplexed connections every hour
|
||||||
$schedule->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
$this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
||||||
|
|
||||||
// Temporary solution until we have better memory management for Sentinel
|
// Temporary solution until we have better memory management for Sentinel
|
||||||
if ($server->isSentinelEnabled()) {
|
if ($server->isSentinelEnabled()) {
|
||||||
$schedule->job(function () use ($server) {
|
$this->scheduleInstance->job(function () use ($server) {
|
||||||
$server->restartContainer('coolify-sentinel');
|
$server->restartContainer('coolify-sentinel');
|
||||||
})->daily()->onOneServer();
|
})->daily()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkScheduledBackups($schedule): void
|
private function checkScheduledBackups(): void
|
||||||
{
|
{
|
||||||
$scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get();
|
$scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get();
|
||||||
if ($scheduled_backups->isEmpty()) {
|
if ($scheduled_backups->isEmpty()) {
|
||||||
@@ -174,13 +187,13 @@ class Kernel extends ConsoleKernel
|
|||||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||||
}
|
}
|
||||||
$schedule->job(new DatabaseBackupJob(
|
$this->scheduleInstance->job(new DatabaseBackupJob(
|
||||||
backup: $scheduled_backup
|
backup: $scheduled_backup
|
||||||
))->cron($scheduled_backup->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
))->cron($scheduled_backup->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkScheduledTasks($schedule): void
|
private function checkScheduledTasks(): void
|
||||||
{
|
{
|
||||||
$scheduled_tasks = ScheduledTask::where('enabled', true)->get();
|
$scheduled_tasks = ScheduledTask::where('enabled', true)->get();
|
||||||
if ($scheduled_tasks->isEmpty()) {
|
if ($scheduled_tasks->isEmpty()) {
|
||||||
@@ -214,7 +227,7 @@ class Kernel extends ConsoleKernel
|
|||||||
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
||||||
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
||||||
}
|
}
|
||||||
$schedule->job(new ScheduledTaskJob(
|
$this->scheduleInstance->job(new ScheduledTaskJob(
|
||||||
task: $scheduled_task
|
task: $scheduled_task
|
||||||
))->cron($scheduled_task->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
))->cron($scheduled_task->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
|
@@ -116,7 +116,7 @@ class ProjectController extends Controller
|
|||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
description: 'Project details',
|
description: 'Environment details',
|
||||||
content: new OA\JsonContent(ref: '#/components/schemas/Environment')),
|
content: new OA\JsonContent(ref: '#/components/schemas/Environment')),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
|
@@ -81,15 +81,8 @@ class SecurityController extends Controller
|
|||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
description: 'Get all private keys.',
|
description: 'Get all private keys.',
|
||||||
content: [
|
content: new OA\JsonContent(ref: '#/components/schemas/PrivateKey')
|
||||||
new OA\MediaType(
|
),
|
||||||
mediaType: 'application/json',
|
|
||||||
schema: new OA\Schema(
|
|
||||||
type: 'array',
|
|
||||||
items: new OA\Items(ref: '#/components/schemas/PrivateKey')
|
|
||||||
)
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
|
@@ -426,6 +426,7 @@ class ServersController extends Controller
|
|||||||
'private_key_uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the private key.'],
|
'private_key_uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the private key.'],
|
||||||
'is_build_server' => ['type' => 'boolean', 'example' => false, 'description' => 'Is build server.'],
|
'is_build_server' => ['type' => 'boolean', 'example' => false, 'description' => 'Is build server.'],
|
||||||
'instant_validate' => ['type' => 'boolean', 'example' => false, 'description' => 'Instant validate.'],
|
'instant_validate' => ['type' => 'boolean', 'example' => false, 'description' => 'Instant validate.'],
|
||||||
|
'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'example' => 'traefik', 'description' => 'The proxy type.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -461,7 +462,7 @@ class ServersController extends Controller
|
|||||||
)]
|
)]
|
||||||
public function create_server(Request $request)
|
public function create_server(Request $request)
|
||||||
{
|
{
|
||||||
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type'];
|
||||||
|
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
@@ -481,6 +482,7 @@ class ServersController extends Controller
|
|||||||
'user' => 'string|nullable',
|
'user' => 'string|nullable',
|
||||||
'is_build_server' => 'boolean|nullable',
|
'is_build_server' => 'boolean|nullable',
|
||||||
'instant_validate' => 'boolean|nullable',
|
'instant_validate' => 'boolean|nullable',
|
||||||
|
'proxy_type' => 'string|nullable',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
@@ -512,6 +514,14 @@ class ServersController extends Controller
|
|||||||
if (is_null($request->instant_validate)) {
|
if (is_null($request->instant_validate)) {
|
||||||
$request->offsetSet('instant_validate', false);
|
$request->offsetSet('instant_validate', false);
|
||||||
}
|
}
|
||||||
|
if ($request->proxy_type) {
|
||||||
|
$validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) {
|
||||||
|
return str($proxyType->value)->lower();
|
||||||
|
});
|
||||||
|
if (! $validProxyTypes->contains(str($request->proxy_type)->lower())) {
|
||||||
|
return response()->json(['message' => 'Invalid proxy type.'], 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
$privateKey = PrivateKey::whereTeamId($teamId)->whereUuid($request->private_key_uuid)->first();
|
$privateKey = PrivateKey::whereTeamId($teamId)->whereUuid($request->private_key_uuid)->first();
|
||||||
if (! $privateKey) {
|
if (! $privateKey) {
|
||||||
return response()->json(['message' => 'Private key not found.'], 404);
|
return response()->json(['message' => 'Private key not found.'], 404);
|
||||||
@@ -521,6 +531,8 @@ class ServersController extends Controller
|
|||||||
return response()->json(['message' => 'Server with this IP already exists.'], 400);
|
return response()->json(['message' => 'Server with this IP already exists.'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$proxyType = $request->proxy_type ? str($request->proxy_type)->upper() : ProxyTypes::TRAEFIK->value;
|
||||||
|
|
||||||
$server = ModelsServer::create([
|
$server = ModelsServer::create([
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
'description' => $request->description,
|
'description' => $request->description,
|
||||||
@@ -530,7 +542,7 @@ class ServersController extends Controller
|
|||||||
'private_key_id' => $privateKey->id,
|
'private_key_id' => $privateKey->id,
|
||||||
'team_id' => $teamId,
|
'team_id' => $teamId,
|
||||||
'proxy' => [
|
'proxy' => [
|
||||||
'type' => ProxyTypes::TRAEFIK->value,
|
'type' => $proxyType,
|
||||||
'status' => ProxyStatus::EXITED->value,
|
'status' => ProxyStatus::EXITED->value,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
@@ -571,6 +583,7 @@ class ServersController extends Controller
|
|||||||
'private_key_uuid' => ['type' => 'string', 'description' => 'The UUID of the private key.'],
|
'private_key_uuid' => ['type' => 'string', 'description' => 'The UUID of the private key.'],
|
||||||
'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'],
|
'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'],
|
||||||
'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'],
|
'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'],
|
||||||
|
'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'description' => 'The proxy type.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -604,7 +617,7 @@ class ServersController extends Controller
|
|||||||
)]
|
)]
|
||||||
public function update_server(Request $request)
|
public function update_server(Request $request)
|
||||||
{
|
{
|
||||||
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type'];
|
||||||
|
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
@@ -624,6 +637,7 @@ class ServersController extends Controller
|
|||||||
'user' => 'string|nullable',
|
'user' => 'string|nullable',
|
||||||
'is_build_server' => 'boolean|nullable',
|
'is_build_server' => 'boolean|nullable',
|
||||||
'instant_validate' => 'boolean|nullable',
|
'instant_validate' => 'boolean|nullable',
|
||||||
|
'proxy_type' => 'string|nullable',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
@@ -644,6 +658,16 @@ class ServersController extends Controller
|
|||||||
if (! $server) {
|
if (! $server) {
|
||||||
return response()->json(['message' => 'Server not found.'], 404);
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
}
|
}
|
||||||
|
if ($request->proxy_type) {
|
||||||
|
$validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) {
|
||||||
|
return str($proxyType->value)->lower();
|
||||||
|
});
|
||||||
|
if ($validProxyTypes->contains(str($request->proxy_type)->lower())) {
|
||||||
|
$server->changeProxy($request->proxy_type, async: true);
|
||||||
|
} else {
|
||||||
|
return response()->json(['message' => 'Invalid proxy type.'], 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
$server->update($request->only(['name', 'description', 'ip', 'port', 'user']));
|
$server->update($request->only(['name', 'description', 'ip', 'port', 'user']));
|
||||||
if ($request->is_build_server) {
|
if ($request->is_build_server) {
|
||||||
$server->settings()->update([
|
$server->settings()->update([
|
||||||
@@ -654,7 +678,9 @@ class ServersController extends Controller
|
|||||||
ValidateServer::dispatch($server)->onQueue('high');
|
ValidateServer::dispatch($server)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(serializeApiResponse($server))->setStatusCode(201);
|
return response()->json([
|
||||||
|
|
||||||
|
])->setStatusCode(201);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OA\Delete(
|
#[OA\Delete(
|
||||||
|
@@ -33,6 +33,7 @@ class Gitlab extends Controller
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$return_payloads = collect([]);
|
$return_payloads = collect([]);
|
||||||
$payload = $request->collect();
|
$payload = $request->collect();
|
||||||
$headers = $request->headers->all();
|
$headers = $request->headers->all();
|
||||||
@@ -48,6 +49,15 @@ class Gitlab extends Controller
|
|||||||
return response($return_payloads);
|
return response($return_payloads);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (empty($x_gitlab_token)) {
|
||||||
|
$return_payloads->push([
|
||||||
|
'status' => 'failed',
|
||||||
|
'message' => 'Invalid signature.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response($return_payloads);
|
||||||
|
}
|
||||||
|
|
||||||
if ($x_gitlab_event === 'push') {
|
if ($x_gitlab_event === 'push') {
|
||||||
$branch = data_get($payload, 'ref');
|
$branch = data_get($payload, 'ref');
|
||||||
$full_name = data_get($payload, 'project.path_with_namespace');
|
$full_name = data_get($payload, 'project.path_with_namespace');
|
||||||
|
@@ -5,8 +5,6 @@ namespace App\Http\Controllers\Webhook;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Jobs\ServerLimitCheckJob;
|
use App\Jobs\ServerLimitCheckJob;
|
||||||
use App\Jobs\SubscriptionInvoiceFailedJob;
|
use App\Jobs\SubscriptionInvoiceFailedJob;
|
||||||
use App\Jobs\SubscriptionTrialEndedJob;
|
|
||||||
use App\Jobs\SubscriptionTrialEndsSoonJob;
|
|
||||||
use App\Models\Subscription;
|
use App\Models\Subscription;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Models\Webhook;
|
use App\Models\Webhook;
|
||||||
@@ -260,42 +258,7 @@ class Stripe extends Controller
|
|||||||
$customerId = data_get($data, 'customer');
|
$customerId = data_get($data, 'customer');
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||||
$team = data_get($subscription, 'team');
|
$team = data_get($subscription, 'team');
|
||||||
if ($team) {
|
$team?->subscriptionEnded();
|
||||||
$team->trialEnded();
|
|
||||||
}
|
|
||||||
$subscription->update([
|
|
||||||
'stripe_subscription_id' => null,
|
|
||||||
'stripe_plan_id' => null,
|
|
||||||
'stripe_cancel_at_period_end' => false,
|
|
||||||
'stripe_invoice_paid' => false,
|
|
||||||
'stripe_trial_already_ended' => false,
|
|
||||||
]);
|
|
||||||
// send_internal_notification('customer.subscription.deleted for customer: '.$customerId);
|
|
||||||
break;
|
|
||||||
case 'customer.subscription.trial_will_end':
|
|
||||||
// Not used for now
|
|
||||||
$customerId = data_get($data, 'customer');
|
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
|
||||||
$team = data_get($subscription, 'team');
|
|
||||||
if (! $team) {
|
|
||||||
return response('No team found for subscription: '.$subscription->id, 400);
|
|
||||||
}
|
|
||||||
SubscriptionTrialEndsSoonJob::dispatch($team);
|
|
||||||
break;
|
|
||||||
case 'customer.subscription.paused':
|
|
||||||
$customerId = data_get($data, 'customer');
|
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
|
||||||
$team = data_get($subscription, 'team');
|
|
||||||
if (! $team) {
|
|
||||||
return response('No team found for subscription: '.$subscription->id, 400);
|
|
||||||
}
|
|
||||||
$team->trialEnded();
|
|
||||||
$subscription->update([
|
|
||||||
'stripe_trial_already_ended' => true,
|
|
||||||
'stripe_invoice_paid' => false,
|
|
||||||
]);
|
|
||||||
SubscriptionTrialEndedJob::dispatch($team);
|
|
||||||
// send_internal_notification('Subscription paused for customer: '.$customerId);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Unhandled event type
|
// Unhandled event type
|
||||||
|
@@ -225,6 +225,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags(): array
|
||||||
|
{
|
||||||
|
return ['server:'.gethostname()];
|
||||||
|
}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
|
@@ -26,7 +26,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server, public bool $manualCleanup = false) {}
|
public function __construct(public Server $server, public bool $manualCleanup = false) {}
|
||||||
|
@@ -28,7 +28,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Models\Team;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class SubscriptionTrialEndedJob implements ShouldBeEncrypted, ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
public Team $team
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$session = getStripeCustomerPortalSession($this->team);
|
|
||||||
$mail = new MailMessage;
|
|
||||||
$mail->subject('Action required: You trial in Coolify Cloud ended.');
|
|
||||||
$mail->view('emails.trial-ended', [
|
|
||||||
'stripeCustomerPortal' => $session->url,
|
|
||||||
]);
|
|
||||||
$this->team->members()->each(function ($member) use ($mail) {
|
|
||||||
if ($member->isAdmin()) {
|
|
||||||
send_user_an_email($mail, $member->email);
|
|
||||||
send_internal_notification('Trial reminder email sent to '.$member->email);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
send_internal_notification('SubscriptionTrialEndsSoonJob failed with: '.$e->getMessage());
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Models\Team;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class SubscriptionTrialEndsSoonJob implements ShouldBeEncrypted, ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
public Team $team
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$session = getStripeCustomerPortalSession($this->team);
|
|
||||||
$mail = new MailMessage;
|
|
||||||
$mail->subject('You trial in Coolify Cloud ends soon.');
|
|
||||||
$mail->view('emails.trial-ends-soon', [
|
|
||||||
'stripeCustomerPortal' => $session->url,
|
|
||||||
]);
|
|
||||||
$this->team->members()->each(function ($member) use ($mail) {
|
|
||||||
if ($member->isAdmin()) {
|
|
||||||
send_user_an_email($mail, $member->email);
|
|
||||||
send_internal_notification('Trial reminder email sent to '.$member->email);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
send_internal_notification('SubscriptionTrialEndsSoonJob failed with: '.$e->getMessage());
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Admin;
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Container\Attributes\Auth as AttributesAuth;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
@@ -43,17 +43,13 @@ class Index extends Component
|
|||||||
|
|
||||||
public function getSubscribers()
|
public function getSubscribers()
|
||||||
{
|
{
|
||||||
$this->inactiveSubscribers = User::whereDoesntHave('teams', function ($query) {
|
$this->inactiveSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', false)->count();
|
||||||
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
$this->activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->count();
|
||||||
})->count();
|
|
||||||
$this->activeSubscribers = User::whereHas('teams', function ($query) {
|
|
||||||
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
|
||||||
})->count();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function switchUser(int $user_id)
|
public function switchUser(int $user_id)
|
||||||
{
|
{
|
||||||
if (AttributesAuth::id() !== 0) {
|
if (Auth::id() !== 0) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$user = User::find($user_id);
|
$user = User::find($user_id);
|
||||||
|
@@ -66,11 +66,15 @@ class Index extends Component
|
|||||||
|
|
||||||
public bool $serverReachable = true;
|
public bool $serverReachable = true;
|
||||||
|
|
||||||
|
public ?string $minDockerVersion = null;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (auth()->user()?->isMember() && auth()->user()->currentTeam()->show_boarding === true) {
|
if (auth()->user()?->isMember() && auth()->user()->currentTeam()->show_boarding === true) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->minDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.');
|
||||||
$this->privateKeyName = generate_random_name();
|
$this->privateKeyName = generate_random_name();
|
||||||
$this->remoteServerName = generate_random_name();
|
$this->remoteServerName = generate_random_name();
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
|
@@ -4,7 +4,6 @@ namespace App\Livewire\Server;
|
|||||||
|
|
||||||
use App\Actions\Proxy\CheckConfiguration;
|
use App\Actions\Proxy\CheckConfiguration;
|
||||||
use App\Actions\Proxy\SaveConfiguration;
|
use App\Actions\Proxy\SaveConfiguration;
|
||||||
use App\Actions\Proxy\StartProxy;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -44,14 +43,13 @@ class Proxy extends Component
|
|||||||
|
|
||||||
public function selectProxy($proxy_type)
|
public function selectProxy($proxy_type)
|
||||||
{
|
{
|
||||||
$this->server->proxy->set('status', 'exited');
|
try {
|
||||||
$this->server->proxy->set('type', $proxy_type);
|
$this->server->changeProxy($proxy_type, async: false);
|
||||||
$this->server->save();
|
$this->selectedProxy = $this->server->proxy->type;
|
||||||
$this->selectedProxy = $this->server->proxy->type;
|
$this->dispatch('proxyStatusUpdated');
|
||||||
if ($this->server->proxySet()) {
|
} catch (\Throwable $e) {
|
||||||
StartProxy::run($this->server, false);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
$this->dispatch('proxyStatusUpdated');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
|
@@ -127,7 +127,14 @@ class Show extends Component
|
|||||||
$this->server->settings->sentinel_custom_url = $this->sentinelCustomUrl;
|
$this->server->settings->sentinel_custom_url = $this->sentinelCustomUrl;
|
||||||
$this->server->settings->is_sentinel_enabled = $this->isSentinelEnabled;
|
$this->server->settings->is_sentinel_enabled = $this->isSentinelEnabled;
|
||||||
$this->server->settings->is_sentinel_debug_enabled = $this->isSentinelDebugEnabled;
|
$this->server->settings->is_sentinel_debug_enabled = $this->isSentinelDebugEnabled;
|
||||||
$this->server->settings->server_timezone = $this->serverTimezone;
|
|
||||||
|
if (! validate_timezone($this->serverTimezone)) {
|
||||||
|
$this->serverTimezone = config('app.timezone');
|
||||||
|
throw new \Exception('Invalid timezone.');
|
||||||
|
} else {
|
||||||
|
$this->server->settings->server_timezone = $this->serverTimezone;
|
||||||
|
}
|
||||||
|
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
} else {
|
} else {
|
||||||
$this->name = $this->server->name;
|
$this->name = $this->server->name;
|
||||||
|
@@ -159,7 +159,8 @@ class ValidateAndInstall extends Component
|
|||||||
$this->dispatch('refreshBoardingIndex');
|
$this->dispatch('refreshBoardingIndex');
|
||||||
$this->dispatch('success', 'Server validated.');
|
$this->dispatch('success', 'Server validated.');
|
||||||
} else {
|
} else {
|
||||||
$this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
$requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.');
|
||||||
|
$this->error = 'Minimum Docker Engine version '.$requiredDockerVersion.' is not instaled. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
$this->server->update([
|
$this->server->update([
|
||||||
'validation_logs' => $this->error,
|
'validation_logs' => $this->error,
|
||||||
]);
|
]);
|
||||||
|
@@ -139,6 +139,14 @@ class Index extends Component
|
|||||||
$error_show = false;
|
$error_show = false;
|
||||||
$this->server = Server::findOrFail(0);
|
$this->server = Server::findOrFail(0);
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
|
|
||||||
|
if (! validate_timezone($this->instance_timezone)) {
|
||||||
|
$this->instance_timezone = config('app.timezone');
|
||||||
|
throw new \Exception('Invalid timezone.');
|
||||||
|
} else {
|
||||||
|
$this->settings->instance_timezone = $this->instance_timezone;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->settings->public_port_min > $this->settings->public_port_max) {
|
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.');
|
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
|
||||||
|
|
||||||
|
@@ -14,13 +14,25 @@ class Index extends Component
|
|||||||
|
|
||||||
public $containers = [];
|
public $containers = [];
|
||||||
|
|
||||||
|
public bool $isLoadingContainers = true;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (! auth()->user()->isAdmin()) {
|
if (! auth()->user()->isAdmin()) {
|
||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
$this->servers = Server::isReachable()->get();
|
$this->servers = Server::isReachable()->get();
|
||||||
$this->containers = $this->getAllActiveContainers();
|
}
|
||||||
|
|
||||||
|
public function loadContainers()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->containers = $this->getAllActiveContainers();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
} finally {
|
||||||
|
$this->isLoadingContainers = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getAllActiveContainers()
|
private function getAllActiveContainers()
|
||||||
|
@@ -906,21 +906,7 @@ class Application extends BaseModel
|
|||||||
|
|
||||||
public function customRepository()
|
public function customRepository()
|
||||||
{
|
{
|
||||||
preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches);
|
return convertGitUrl($this->git_repository, $this->deploymentType(), $this->source);
|
||||||
$port = 22;
|
|
||||||
if (count($matches) === 1) {
|
|
||||||
$port = $matches[0];
|
|
||||||
$gitHost = str($this->git_repository)->before(':');
|
|
||||||
$gitRepo = str($this->git_repository)->after('/');
|
|
||||||
$repository = "$gitHost:$gitRepo";
|
|
||||||
} else {
|
|
||||||
$repository = $this->git_repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'repository' => $repository,
|
|
||||||
'port' => $port,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateBaseDir(string $uuid)
|
public function generateBaseDir(string $uuid)
|
||||||
@@ -953,6 +939,122 @@ class Application extends BaseModel
|
|||||||
return $git_clone_command;
|
return $git_clone_command;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getGitRemoteStatus(string $deployment_uuid)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
['commands' => $lsRemoteCommand] = $this->generateGitLsRemoteCommands(deployment_uuid: $deployment_uuid, exec_in_docker: false);
|
||||||
|
instant_remote_process([$lsRemoteCommand], $this->destination->server, true);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'is_accessible' => true,
|
||||||
|
'error' => null,
|
||||||
|
];
|
||||||
|
} catch (\RuntimeException $ex) {
|
||||||
|
return [
|
||||||
|
'is_accessible' => false,
|
||||||
|
'error' => $ex->getMessage(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateGitLsRemoteCommands(string $deployment_uuid, bool $exec_in_docker = true)
|
||||||
|
{
|
||||||
|
$branch = $this->git_branch;
|
||||||
|
['repository' => $customRepository, 'port' => $customPort] = $this->customRepository();
|
||||||
|
$commands = collect([]);
|
||||||
|
$base_command = 'git ls-remote';
|
||||||
|
|
||||||
|
if ($this->deploymentType() === 'source') {
|
||||||
|
$source_html_url = data_get($this, 'source.html_url');
|
||||||
|
$url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL));
|
||||||
|
$source_html_url_host = $url['host'];
|
||||||
|
$source_html_url_scheme = $url['scheme'];
|
||||||
|
|
||||||
|
if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
|
||||||
|
if ($this->source->is_public) {
|
||||||
|
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
|
||||||
|
$base_command = "{$base_command} {$this->source->html_url}/{$customRepository}";
|
||||||
|
} else {
|
||||||
|
$github_access_token = generate_github_installation_token($this->source);
|
||||||
|
|
||||||
|
if ($exec_in_docker) {
|
||||||
|
$base_command = "{$base_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git";
|
||||||
|
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git";
|
||||||
|
} else {
|
||||||
|
$base_command = "{$base_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
|
||||||
|
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exec_in_docker) {
|
||||||
|
$commands->push(executeInDocker($deployment_uuid, $base_command));
|
||||||
|
} else {
|
||||||
|
$commands->push($base_command);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'commands' => $commands->implode(' && '),
|
||||||
|
'branch' => $branch,
|
||||||
|
'fullRepoUrl' => $fullRepoUrl,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->deploymentType() === 'deploy_key') {
|
||||||
|
$fullRepoUrl = $customRepository;
|
||||||
|
$private_key = data_get($this, 'private_key.private_key');
|
||||||
|
if (is_null($private_key)) {
|
||||||
|
throw new RuntimeException('Private key not found. Please add a private key to the application and try again.');
|
||||||
|
}
|
||||||
|
$private_key = base64_encode($private_key);
|
||||||
|
$base_comamnd = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$base_command} {$customRepository}";
|
||||||
|
|
||||||
|
if ($exec_in_docker) {
|
||||||
|
$commands = collect([
|
||||||
|
executeInDocker($deployment_uuid, 'mkdir -p /root/.ssh'),
|
||||||
|
executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"),
|
||||||
|
executeInDocker($deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$commands = collect([
|
||||||
|
'mkdir -p /root/.ssh',
|
||||||
|
"echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null",
|
||||||
|
'chmod 600 /root/.ssh/id_rsa',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exec_in_docker) {
|
||||||
|
$commands->push(executeInDocker($deployment_uuid, $base_comamnd));
|
||||||
|
} else {
|
||||||
|
$commands->push($base_comamnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'commands' => $commands->implode(' && '),
|
||||||
|
'branch' => $branch,
|
||||||
|
'fullRepoUrl' => $fullRepoUrl,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->deploymentType() === 'other') {
|
||||||
|
$fullRepoUrl = $customRepository;
|
||||||
|
$base_command = "{$base_command} {$customRepository}";
|
||||||
|
$base_command = $this->setGitImportSettings($deployment_uuid, $base_command, public: true);
|
||||||
|
|
||||||
|
if ($exec_in_docker) {
|
||||||
|
$commands->push(executeInDocker($deployment_uuid, $base_command));
|
||||||
|
} else {
|
||||||
|
$commands->push($base_command);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'commands' => $commands->implode(' && '),
|
||||||
|
'branch' => $branch,
|
||||||
|
'fullRepoUrl' => $fullRepoUrl,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null)
|
public function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null)
|
||||||
{
|
{
|
||||||
$branch = $this->git_branch;
|
$branch = $this->git_branch;
|
||||||
@@ -1214,6 +1316,11 @@ class Application extends BaseModel
|
|||||||
$workdir = rtrim($this->base_directory, '/');
|
$workdir = rtrim($this->base_directory, '/');
|
||||||
$composeFile = $this->docker_compose_location;
|
$composeFile = $this->docker_compose_location;
|
||||||
$fileList = collect([".$workdir$composeFile"]);
|
$fileList = collect([".$workdir$composeFile"]);
|
||||||
|
$gitRemoteStatus = $this->getGitRemoteStatus(deployment_uuid: $uuid);
|
||||||
|
if (! $gitRemoteStatus['is_accessible']) {
|
||||||
|
throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}");
|
||||||
|
}
|
||||||
|
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
"rm -rf /tmp/{$uuid}",
|
"rm -rf /tmp/{$uuid}",
|
||||||
"mkdir -p /tmp/{$uuid}",
|
"mkdir -p /tmp/{$uuid}",
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Actions\Server\InstallDocker;
|
use App\Actions\Server\InstallDocker;
|
||||||
use App\Actions\Server\StartSentinel;
|
use App\Actions\Server\StartSentinel;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
@@ -26,22 +27,23 @@ use Symfony\Component\Yaml\Yaml;
|
|||||||
description: 'Server model',
|
description: 'Server model',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'id' => ['type' => 'integer'],
|
'id' => ['type' => 'integer', 'description' => 'The server ID.'],
|
||||||
'uuid' => ['type' => 'string'],
|
'uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||||
'name' => ['type' => 'string'],
|
'name' => ['type' => 'string', 'description' => 'The server name.'],
|
||||||
'description' => ['type' => 'string'],
|
'description' => ['type' => 'string', 'description' => 'The server description.'],
|
||||||
'ip' => ['type' => 'string'],
|
'ip' => ['type' => 'string', 'description' => 'The IP address.'],
|
||||||
'user' => ['type' => 'string'],
|
'user' => ['type' => 'string', 'description' => 'The user.'],
|
||||||
'port' => ['type' => 'integer'],
|
'port' => ['type' => 'integer', 'description' => 'The port number.'],
|
||||||
'proxy' => ['type' => 'object'],
|
'proxy' => ['type' => 'object', 'description' => 'The proxy configuration.'],
|
||||||
'high_disk_usage_notification_sent' => ['type' => 'boolean'],
|
'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'description' => 'The proxy type.'],
|
||||||
'unreachable_notification_sent' => ['type' => 'boolean'],
|
'high_disk_usage_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the high disk usage notification has been sent.'],
|
||||||
'unreachable_count' => ['type' => 'integer'],
|
'unreachable_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unreachable notification has been sent.'],
|
||||||
'validation_logs' => ['type' => 'string'],
|
'unreachable_count' => ['type' => 'integer', 'description' => 'The unreachable count for your server.'],
|
||||||
'log_drain_notification_sent' => ['type' => 'boolean'],
|
'validation_logs' => ['type' => 'string', 'description' => 'The validation logs.'],
|
||||||
'swarm_cluster' => ['type' => 'string'],
|
'log_drain_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the log drain notification has been sent.'],
|
||||||
'delete_unused_volumes' => ['type' => 'boolean'],
|
'swarm_cluster' => ['type' => 'string', 'description' => 'The swarm cluster configuration.'],
|
||||||
'delete_unused_networks' => ['type' => 'boolean'],
|
'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'],
|
||||||
|
'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'],
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
|
|
||||||
@@ -1251,4 +1253,25 @@ $schema://$host {
|
|||||||
{
|
{
|
||||||
return instant_remote_process(['docker restart '.$containerName], $this, false);
|
return instant_remote_process(['docker restart '.$containerName], $this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function changeProxy(string $proxyType, bool $async = true)
|
||||||
|
{
|
||||||
|
$validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) {
|
||||||
|
return str($proxyType->value)->lower();
|
||||||
|
});
|
||||||
|
if ($validProxyTypes->contains(str($proxyType)->lower())) {
|
||||||
|
$this->proxy->set('type', str($proxyType)->upper());
|
||||||
|
$this->proxy->set('status', 'exited');
|
||||||
|
$this->save();
|
||||||
|
if ($this->proxySet()) {
|
||||||
|
if ($async) {
|
||||||
|
StartProxy::dispatch($this);
|
||||||
|
} else {
|
||||||
|
StartProxy::run($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Invalid proxy type.');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -257,8 +257,15 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
return $this->hasMany(S3Storage::class)->where('is_usable', true);
|
return $this->hasMany(S3Storage::class)->where('is_usable', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function trialEnded()
|
public function subscriptionEnded()
|
||||||
{
|
{
|
||||||
|
$this->subscription->update([
|
||||||
|
'stripe_subscription_id' => null,
|
||||||
|
'stripe_plan_id' => null,
|
||||||
|
'stripe_cancel_at_period_end' => false,
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
]);
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
$server->settings()->update([
|
$server->settings()->update([
|
||||||
'is_usable' => false,
|
'is_usable' => false,
|
||||||
@@ -267,16 +274,6 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function trialEndedButSubscribed()
|
|
||||||
{
|
|
||||||
foreach ($this->servers as $server) {
|
|
||||||
$server->settings()->update([
|
|
||||||
'is_usable' => true,
|
|
||||||
'is_reachable' => true,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAnyNotificationEnabled()
|
public function isAnyNotificationEnabled()
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
|
@@ -109,7 +109,8 @@ function format_docker_envs_to_json($rawOutput)
|
|||||||
function checkMinimumDockerEngineVersion($dockerVersion)
|
function checkMinimumDockerEngineVersion($dockerVersion)
|
||||||
{
|
{
|
||||||
$majorDockerVersion = str($dockerVersion)->before('.')->value();
|
$majorDockerVersion = str($dockerVersion)->before('.')->value();
|
||||||
if ($majorDockerVersion <= 22) {
|
$requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.')->value();
|
||||||
|
if ($majorDockerVersion < $requiredDockerVersion) {
|
||||||
$dockerVersion = null;
|
$dockerVersion = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,15 +226,13 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
|||||||
case $type?->contains('minio'):
|
case $type?->contains('minio'):
|
||||||
$MINIO_BROWSER_REDIRECT_URL = $variables->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first();
|
$MINIO_BROWSER_REDIRECT_URL = $variables->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first();
|
||||||
$MINIO_SERVER_URL = $variables->where('key', 'MINIO_SERVER_URL')->first();
|
$MINIO_SERVER_URL = $variables->where('key', 'MINIO_SERVER_URL')->first();
|
||||||
if (is_null($MINIO_BROWSER_REDIRECT_URL) || is_null($MINIO_SERVER_URL)) {
|
|
||||||
return $payload;
|
if (str($MINIO_BROWSER_REDIRECT_URL->value)->isEmpty()) {
|
||||||
}
|
|
||||||
if (is_null($MINIO_BROWSER_REDIRECT_URL?->value)) {
|
|
||||||
$MINIO_BROWSER_REDIRECT_URL?->update([
|
$MINIO_BROWSER_REDIRECT_URL?->update([
|
||||||
'value' => generateFqdn($server, 'console-'.$uuid, true),
|
'value' => generateFqdn($server, 'console-'.$uuid, true),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (is_null($MINIO_SERVER_URL?->value)) {
|
if (str($MINIO_SERVER_URL->value)->isEmpty()) {
|
||||||
$MINIO_SERVER_URL?->update([
|
$MINIO_SERVER_URL?->update([
|
||||||
'value' => generateFqdn($server, 'minio-'.$uuid, true),
|
'value' => generateFqdn($server, 'minio-'.$uuid, true),
|
||||||
]);
|
]);
|
||||||
@@ -246,15 +245,13 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
|||||||
case $type?->contains('logto'):
|
case $type?->contains('logto'):
|
||||||
$LOGTO_ENDPOINT = $variables->where('key', 'LOGTO_ENDPOINT')->first();
|
$LOGTO_ENDPOINT = $variables->where('key', 'LOGTO_ENDPOINT')->first();
|
||||||
$LOGTO_ADMIN_ENDPOINT = $variables->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
|
$LOGTO_ADMIN_ENDPOINT = $variables->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
|
||||||
if (is_null($LOGTO_ENDPOINT) || is_null($LOGTO_ADMIN_ENDPOINT)) {
|
|
||||||
return $payload;
|
if (str($LOGTO_ENDPOINT?->value)->isEmpty()) {
|
||||||
}
|
|
||||||
if (is_null($LOGTO_ENDPOINT?->value)) {
|
|
||||||
$LOGTO_ENDPOINT?->update([
|
$LOGTO_ENDPOINT?->update([
|
||||||
'value' => generateFqdn($server, 'logto-'.$uuid),
|
'value' => generateFqdn($server, 'logto-'.$uuid),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (is_null($LOGTO_ADMIN_ENDPOINT?->value)) {
|
if (str($LOGTO_ADMIN_ENDPOINT?->value)->isEmpty()) {
|
||||||
$LOGTO_ADMIN_ENDPOINT?->update([
|
$LOGTO_ADMIN_ENDPOINT?->update([
|
||||||
'value' => generateFqdn($server, 'logto-admin-'.$uuid),
|
'value' => generateFqdn($server, 'logto-admin-'.$uuid),
|
||||||
]);
|
]);
|
||||||
|
@@ -7,6 +7,7 @@ use App\Models\Application;
|
|||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
|
use App\Models\GithubApp;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\LocalFileVolume;
|
use App\Models\LocalFileVolume;
|
||||||
use App\Models\LocalPersistentVolume;
|
use App\Models\LocalPersistentVolume;
|
||||||
@@ -384,6 +385,11 @@ function validate_cron_expression($expression_to_validate): bool
|
|||||||
|
|
||||||
return $isValid;
|
return $isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validate_timezone(string $timezone): bool
|
||||||
|
{
|
||||||
|
return in_array($timezone, timezone_identifiers_list());
|
||||||
|
}
|
||||||
function send_internal_notification(string $message): void
|
function send_internal_notification(string $message): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -4092,3 +4098,53 @@ function defaultNginxConfiguration(): string
|
|||||||
}
|
}
|
||||||
}';
|
}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convertGitUrl(string $gitRepository, string $deploymentType, ?GithubApp $source = null): array
|
||||||
|
{
|
||||||
|
$repository = $gitRepository;
|
||||||
|
$providerInfo = [
|
||||||
|
'host' => null,
|
||||||
|
'user' => 'git',
|
||||||
|
'port' => 22,
|
||||||
|
'repository' => $gitRepository,
|
||||||
|
];
|
||||||
|
$sshMatches = [];
|
||||||
|
$matches = [];
|
||||||
|
|
||||||
|
// Let's try and parse the string to detect if it's a valid SSH string or not
|
||||||
|
preg_match('/((.*?)\:\/\/)?(.*@.*:.*)/', $gitRepository, $sshMatches);
|
||||||
|
|
||||||
|
if ($deploymentType === 'deploy_key' && empty($sshMatches) && $source) {
|
||||||
|
// If this happens, the user may have provided an HTTP URL when they needed an SSH one
|
||||||
|
// Let's try and fix that for known Git providers
|
||||||
|
switch ($source->getMorphClass()) {
|
||||||
|
case \App\Models\GithubApp::class:
|
||||||
|
$providerInfo['host'] = Url::fromString($source->html_url)->getHost();
|
||||||
|
$providerInfo['port'] = $source->custom_port;
|
||||||
|
$providerInfo['user'] = $source->custom_user;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (! empty($providerInfo['host'])) {
|
||||||
|
// Until we do not support more providers with App (like GithubApp), this will be always true, port will be 22
|
||||||
|
if ($providerInfo['port'] === 22) {
|
||||||
|
$repository = "{$providerInfo['user']}@{$providerInfo['host']}:{$providerInfo['repository']}";
|
||||||
|
} else {
|
||||||
|
$repository = "ssh://{$providerInfo['user']}@{$providerInfo['host']}:{$providerInfo['port']}/{$providerInfo['repository']}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match('/(?<=:)\d+(?=\/)/', $gitRepository, $matches);
|
||||||
|
|
||||||
|
if (count($matches) === 1) {
|
||||||
|
$providerInfo['port'] = $matches[0];
|
||||||
|
$gitHost = str($gitRepository)->before(':');
|
||||||
|
$gitRepo = str($gitRepository)->after('/');
|
||||||
|
$repository = "$gitHost:$gitRepo";
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'repository' => $repository,
|
||||||
|
'port' => $providerInfo['port'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
|
"3sidedcube/laravel-redoc": "^1.0",
|
||||||
"danharrin/livewire-rate-limiting": "^1.1",
|
"danharrin/livewire-rate-limiting": "^1.1",
|
||||||
"doctrine/dbal": "^3.6",
|
"doctrine/dbal": "^3.6",
|
||||||
"guzzlehttp/guzzle": "^7.5.0",
|
"guzzlehttp/guzzle": "^7.5.0",
|
||||||
|
60
composer.lock
generated
60
composer.lock
generated
@@ -4,8 +4,66 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "3f2342fe6b1ba920c8875f8a8fe41962",
|
"content-hash": "b9f4772191b4680e6f92fa9c7c396b10",
|
||||||
"packages": [
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "3sidedcube/laravel-redoc",
|
||||||
|
"version": "v1.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/3sidedcube/laravel-redoc.git",
|
||||||
|
"reference": "c33a563885dcdf1e0f623df5a56c106d130261da"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/3sidedcube/laravel-redoc/zipball/c33a563885dcdf1e0f623df5a56c106d130261da",
|
||||||
|
"reference": "c33a563885dcdf1e0f623df5a56c106d130261da",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/routing": "^8.0|^9.0|^10.0|^11.0",
|
||||||
|
"illuminate/support": "^8.0|^9.0|^10.0|^11.0",
|
||||||
|
"php": "^7.4|^8.0|^8.1|^8.2|^8.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.3",
|
||||||
|
"orchestra/testbench": "^6.0|^7.0|^8.0|^9.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"ThreeSidedCube\\LaravelRedoc\\RedocServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"ThreeSidedCube\\LaravelRedoc\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ben Sherred",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A lightweight package for rendering API documentation using OpenAPI and Redoc.",
|
||||||
|
"homepage": "https://github.com/3sidedcube/laravel-redoc",
|
||||||
|
"keywords": [
|
||||||
|
"3sidedcube",
|
||||||
|
"laravel-redoc"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/3sidedcube/laravel-redoc/issues",
|
||||||
|
"source": "https://github.com/3sidedcube/laravel-redoc/tree/v1.0.1"
|
||||||
|
},
|
||||||
|
"time": "2024-05-20T11:37:55+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "amphp/amp",
|
"name": "amphp/amp",
|
||||||
"version": "v3.0.2",
|
"version": "v3.0.2",
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'docker_install_version' => '26.0',
|
|
||||||
'docs' => [
|
'docs' => [
|
||||||
'base_url' => 'https://coolify.io/docs',
|
'base_url' => 'https://coolify.io/docs',
|
||||||
'contact' => 'https://coolify.io/docs/contact',
|
'contact' => 'https://coolify.io/docs/contact',
|
||||||
@@ -13,6 +12,9 @@ return [
|
|||||||
'server_interval' => 20,
|
'server_interval' => 20,
|
||||||
'command_timeout' => 7200,
|
'command_timeout' => 7200,
|
||||||
],
|
],
|
||||||
|
'docker' => [
|
||||||
|
'minimum_required_version' => '26.0',
|
||||||
|
],
|
||||||
'waitlist' => [
|
'waitlist' => [
|
||||||
'expiration' => 10,
|
'expiration' => 10,
|
||||||
],
|
],
|
||||||
|
28
config/redoc.php
Normal file
28
config/redoc.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Directory
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The name of the directory where your OpenAPI definitions are stored.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'directory' => '',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Variables
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| You can automatically replace variables in your OpenAPI definitions by
|
||||||
|
| adding a key value pair to the array below. This will replace any
|
||||||
|
| instances of :key with the given value.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'variables' => [],
|
||||||
|
|
||||||
|
];
|
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.367',
|
'release' => '4.0.0-beta.368',
|
||||||
|
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.367';
|
return '4.0.0-beta.368';
|
||||||
|
7985
openapi.json
Normal file
7985
openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
48
openapi.yaml
48
openapi.yaml
@@ -3311,7 +3311,7 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: 'Project details'
|
description: 'Environment details'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
@@ -3467,9 +3467,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: array
|
$ref: '#/components/schemas/PrivateKey'
|
||||||
items:
|
|
||||||
$ref: '#/components/schemas/PrivateKey'
|
|
||||||
'401':
|
'401':
|
||||||
$ref: '#/components/responses/401'
|
$ref: '#/components/responses/401'
|
||||||
'400':
|
'400':
|
||||||
@@ -3579,6 +3577,11 @@ paths:
|
|||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
description: 'Instant validate.'
|
description: 'Instant validate.'
|
||||||
|
proxy_type:
|
||||||
|
type: string
|
||||||
|
enum: [traefik, caddy, none]
|
||||||
|
example: traefik
|
||||||
|
description: 'The proxy type.'
|
||||||
type: object
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
@@ -3699,6 +3702,10 @@ paths:
|
|||||||
instant_validate:
|
instant_validate:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'Instant validate.'
|
description: 'Instant validate.'
|
||||||
|
proxy_type:
|
||||||
|
type: string
|
||||||
|
enum: [traefik, caddy, none]
|
||||||
|
description: 'The proxy type.'
|
||||||
type: object
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
@@ -4759,6 +4766,10 @@ components:
|
|||||||
compose_parsing_version:
|
compose_parsing_version:
|
||||||
type: string
|
type: string
|
||||||
description: 'How Coolify parse the compose file.'
|
description: 'How Coolify parse the compose file.'
|
||||||
|
custom_nginx_configuration:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: 'Custom Nginx configuration base64 encoded.'
|
||||||
type: object
|
type: object
|
||||||
ApplicationDeploymentQueue:
|
ApplicationDeploymentQueue:
|
||||||
description: 'Project model'
|
description: 'Project model'
|
||||||
@@ -4909,36 +4920,59 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
|
description: 'The server ID.'
|
||||||
uuid:
|
uuid:
|
||||||
type: string
|
type: string
|
||||||
|
description: 'The server UUID.'
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
description: 'The server name.'
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
|
description: 'The server description.'
|
||||||
ip:
|
ip:
|
||||||
type: string
|
type: string
|
||||||
|
description: 'The IP address.'
|
||||||
user:
|
user:
|
||||||
type: string
|
type: string
|
||||||
|
description: 'The user.'
|
||||||
port:
|
port:
|
||||||
type: integer
|
type: integer
|
||||||
|
description: 'The port number.'
|
||||||
proxy:
|
proxy:
|
||||||
type: object
|
type: object
|
||||||
|
description: 'The proxy configuration.'
|
||||||
|
proxy_type:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- traefik
|
||||||
|
- caddy
|
||||||
|
- none
|
||||||
|
description: 'The proxy type.'
|
||||||
high_disk_usage_notification_sent:
|
high_disk_usage_notification_sent:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
description: 'The flag to indicate if the high disk usage notification has been sent.'
|
||||||
unreachable_notification_sent:
|
unreachable_notification_sent:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
description: 'The flag to indicate if the unreachable notification has been sent.'
|
||||||
unreachable_count:
|
unreachable_count:
|
||||||
type: integer
|
type: integer
|
||||||
|
description: 'The unreachable count for your server.'
|
||||||
validation_logs:
|
validation_logs:
|
||||||
type: string
|
type: string
|
||||||
|
description: 'The validation logs.'
|
||||||
log_drain_notification_sent:
|
log_drain_notification_sent:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
description: 'The flag to indicate if the log drain notification has been sent.'
|
||||||
swarm_cluster:
|
swarm_cluster:
|
||||||
type: string
|
type: string
|
||||||
|
description: 'The swarm cluster configuration.'
|
||||||
delete_unused_volumes:
|
delete_unused_volumes:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
description: 'The flag to indicate if the unused volumes should be deleted.'
|
||||||
delete_unused_networks:
|
delete_unused_networks:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
description: 'The flag to indicate if the unused networks should be deleted.'
|
||||||
type: object
|
type: object
|
||||||
ServerSetting:
|
ServerSetting:
|
||||||
description: 'Server Settings model'
|
description: 'Server Settings model'
|
||||||
@@ -5136,6 +5170,9 @@ components:
|
|||||||
smtp_notifications_database_backups:
|
smtp_notifications_database_backups:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'Whether to send database backup notifications via SMTP.'
|
description: 'Whether to send database backup notifications via SMTP.'
|
||||||
|
smtp_notifications_server_disk_usage:
|
||||||
|
type: boolean
|
||||||
|
description: 'Whether to send server disk usage notifications via SMTP.'
|
||||||
discord_enabled:
|
discord_enabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'Whether Discord is enabled or not.'
|
description: 'Whether Discord is enabled or not.'
|
||||||
@@ -5157,6 +5194,9 @@ components:
|
|||||||
discord_notifications_scheduled_tasks:
|
discord_notifications_scheduled_tasks:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'Whether to send scheduled task notifications via Discord.'
|
description: 'Whether to send scheduled task notifications via Discord.'
|
||||||
|
discord_notifications_server_disk_usage:
|
||||||
|
type: boolean
|
||||||
|
description: 'Whether to send server disk usage notifications via Discord.'
|
||||||
show_boarding:
|
show_boarding:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'Whether to show the boarding screen or not.'
|
description: 'Whether to show the boarding screen or not.'
|
||||||
|
@@ -14,8 +14,8 @@
|
|||||||
'w-full' => $fullWidth,
|
'w-full' => $fullWidth,
|
||||||
])>
|
])>
|
||||||
@if (!$hideLabel)
|
@if (!$hideLabel)
|
||||||
<label @class(['flex gap-4 px-0 min-w-fit label', 'opacity-40' => $disabled])>
|
<label @class(['flex gap-4 items-center px-0 min-w-fit label w-full cursor-pointer', 'opacity-40' => $disabled])>
|
||||||
<span class="flex gap-2">
|
<span class="flex flex-grow gap-2">
|
||||||
@if ($label)
|
@if ($label)
|
||||||
{!! $label !!}
|
{!! $label !!}
|
||||||
@else
|
@else
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
<x-helper :helper="$helper" />
|
<x-helper :helper="$helper" />
|
||||||
@endif
|
@endif
|
||||||
</span>
|
</span>
|
||||||
|
@endif
|
||||||
|
<input @disabled($disabled) type="checkbox" {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||||
|
@if ($instantSave) wire:loading.attr="disabled" wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}'
|
||||||
|
wire:model={{ $id }} @else wire:model={{ $value ?? $id }} @endif />
|
||||||
|
@if (!$hideLabel)
|
||||||
</label>
|
</label>
|
||||||
@endif
|
@endif
|
||||||
<span class="flex-grow"></span>
|
|
||||||
<input @disabled($disabled) type="checkbox" {{ $attributes->merge(['class' => $defaultClass]) }}
|
|
||||||
@if ($instantSave) wire:loading.attr="disabled" wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}'
|
|
||||||
wire:model={{ $id }} @else wire:model={{ $value ?? $id }} @endif />
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div class="flex flex-col items-center justify-center h-full">
|
<div class="flex flex-col items-center justify-center h-full">
|
||||||
<div>
|
<div>
|
||||||
<p class="font-mono font-semibold text-red-500 text-7xl">500</p>
|
<p class="font-mono font-semibold text-red-500 text-7xl">500</p>
|
||||||
<h1 class="mt-4 font-bold tracking-tight dark:text-white">Something is not okay, are you okay?</h1>
|
<h1 class="mt-4 font-bold tracking-tight dark:text-white">Wait, this is not cool...</h1>
|
||||||
<p class="text-base leading-7 text-neutral-300">There has been an error, we are working on it.
|
<p class="text-base leading-7 text-neutral-300">There has been an error, we are working on it.
|
||||||
</p>
|
</p>
|
||||||
@if ($exception->getMessage() !== '')
|
@if ($exception->getMessage() !== '')
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
@if ($foundUsers->count() > 0)
|
@if ($foundUsers->count() > 0)
|
||||||
<div class="flex flex-wrap gap-2 pt-4">
|
<div class="flex flex-wrap gap-2 pt-4">
|
||||||
@foreach ($foundUsers as $user)
|
@foreach ($foundUsers as $user)
|
||||||
<div class="box w-64 group">
|
<div class="box w-64 group" wire:click="switchUser({{ $user->id }})">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="box-title">{{ $user->name }}</div>
|
<div class="box-title">{{ $user->name }}</div>
|
||||||
<div class="box-description">{{ $user->email }}</div>
|
<div class="box-description">{{ $user->email }}</div>
|
||||||
|
@@ -323,7 +323,7 @@
|
|||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
||||||
to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install
|
to run optimal.<br><br>Minimum Docker Engine version is: {{ $minDockerVersion }}<br><br>To manually install
|
||||||
Docker
|
Docker
|
||||||
Engine, check <a target="_blank" class="underline dark:text-warning"
|
Engine, check <a target="_blank" class="underline dark:text-warning"
|
||||||
href="https://docs.docker.com/engine/install/#server">this
|
href="https://docs.docker.com/engine/install/#server">this
|
||||||
|
@@ -44,17 +44,108 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@if ($environment->isEmpty())
|
@if ($environment->isEmpty())
|
||||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_name' => data_get($parameters, 'environment_name')]) }} "
|
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_name' => data_get($parameters, 'environment_name')]) }} "
|
||||||
class="items-center justify-center box">+ Add New Resource</a>
|
class="items-center justify-center box">+ Add New Resource</a>
|
||||||
@else
|
@else
|
||||||
<div x-data="searchComponent()">
|
<div x-data="searchComponent()">
|
||||||
<x-forms.input placeholder="Search for name, fqdn..." x-model="search" id="null" />
|
<x-forms.input placeholder="Search for name, fqdn..." x-model="search" id="null" />
|
||||||
<div class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
<template
|
||||||
<template x-if="allFilteredItems.length === 0">
|
x-if="filteredApplications.length === 0 && filteredDatabases.length === 0 && filteredServices.length === 0">
|
||||||
<div>No resource found with the search term "<span x-text="search"></span>".</div>
|
<div>No resource found with the search term "<span x-text="search"></span>".</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template x-for="item in allFilteredItems" :key="item.uuid">
|
<template x-if="filteredApplications.length > 0">
|
||||||
|
<h2 class="pt-4">Applications</h2>
|
||||||
|
</template>
|
||||||
|
<div x-show="filteredApplications.length > 0"
|
||||||
|
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||||
|
<template x-for="item in filteredApplications" :key="item.uuid">
|
||||||
|
<span>
|
||||||
|
<a class="h-24 box group" :href="item.hrefLink">
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<div class="flex gap-2 px-4">
|
||||||
|
<div class="pb-2 truncate box-title" x-text="item.name"></div>
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
<template x-if="item.status.startsWith('running')">
|
||||||
|
<div title="running" class="bg-success badge badge-absolute"></div>
|
||||||
|
</template>
|
||||||
|
<template x-if="item.status.startsWith('exited')">
|
||||||
|
<div title="exited" class="bg-error badge badge-absolute"></div>
|
||||||
|
</template>
|
||||||
|
<template x-if="item.status.startsWith('restarting')">
|
||||||
|
<div title="restarting" class="bg-warning badge badge-absolute"></div>
|
||||||
|
</template>
|
||||||
|
<template x-if="item.status.startsWith('degraded')">
|
||||||
|
<div title="degraded" class="bg-warning badge badge-absolute"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
|
||||||
|
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
|
||||||
|
<template x-if="item.server_status == false">
|
||||||
|
<div class="px-4 text-xs font-bold text-error">The underlying server has problems
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div
|
||||||
|
class="flex flex-wrap gap-1 pt-1 group-hover:dark:text-white group-hover:text-black group min-h-6">
|
||||||
|
<template x-for="tag in item.tags">
|
||||||
|
<div class="tag" @click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
|
||||||
|
</template>
|
||||||
|
<div class="add-tag" @click.prevent="goto(item)">Add tag</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<template x-if="filteredDatabases.length > 0">
|
||||||
|
<h2 class="pt-4">Databases</h2>
|
||||||
|
</template>
|
||||||
|
<div x-show="filteredDatabases.length > 0"
|
||||||
|
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||||
|
<template x-for="item in filteredDatabases" :key="item.uuid">
|
||||||
|
<span>
|
||||||
|
<a class="h-24 box group" :href="item.hrefLink">
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<div class="flex gap-2 px-4">
|
||||||
|
<div class="pb-2 truncate box-title" x-text="item.name"></div>
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
<template x-if="item.status.startsWith('running')">
|
||||||
|
<div title="running" class="bg-success badge badge-absolute"></div>
|
||||||
|
</template>
|
||||||
|
<template x-if="item.status.startsWith('exited')">
|
||||||
|
<div title="exited" class="bg-error badge badge-absolute"></div>
|
||||||
|
</template>
|
||||||
|
<template x-if="item.status.startsWith('restarting')">
|
||||||
|
<div title="restarting" class="bg-warning badge badge-absolute"></div>
|
||||||
|
</template>
|
||||||
|
<template x-if="item.status.startsWith('degraded')">
|
||||||
|
<div title="degraded" class="bg-warning badge badge-absolute"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
|
||||||
|
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
|
||||||
|
<template x-if="item.server_status == false">
|
||||||
|
<div class="px-4 text-xs font-bold text-error">The underlying server has problems
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div
|
||||||
|
class="flex flex-wrap gap-1 pt-1 group-hover:dark:text-white group-hover:text-black group min-h-6">
|
||||||
|
<template x-for="tag in item.tags">
|
||||||
|
<div class="tag" @click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
|
||||||
|
</template>
|
||||||
|
<div class="add-tag" @click.prevent="goto(item)">Add tag</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<template x-if="filteredServices.length > 0">
|
||||||
|
<h2 class="pt-4">Services</h2>
|
||||||
|
</template>
|
||||||
|
<div x-show="filteredServices.length > 0"
|
||||||
|
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||||
|
<template x-for="item in filteredServices" :key="item.uuid">
|
||||||
<span>
|
<span>
|
||||||
<a class="h-24 box group" :href="item.hrefLink">
|
<a class="h-24 box group" :href="item.hrefLink">
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
@@ -134,9 +225,11 @@
|
|||||||
item.tags?.some(tag => tag.name.toLowerCase().includes(searchLower)));
|
item.tags?.some(tag => tag.name.toLowerCase().includes(searchLower)));
|
||||||
}).sort(sortFn);
|
}).sort(sortFn);
|
||||||
},
|
},
|
||||||
get allFilteredItems() {
|
get filteredApplications() {
|
||||||
|
return this.filterAndSort(this.applications)
|
||||||
|
},
|
||||||
|
get filteredDatabases() {
|
||||||
return [
|
return [
|
||||||
this.applications,
|
|
||||||
this.postgresqls,
|
this.postgresqls,
|
||||||
this.redis,
|
this.redis,
|
||||||
this.mongodbs,
|
this.mongodbs,
|
||||||
@@ -145,8 +238,10 @@
|
|||||||
this.keydbs,
|
this.keydbs,
|
||||||
this.dragonflies,
|
this.dragonflies,
|
||||||
this.clickhouses,
|
this.clickhouses,
|
||||||
this.services
|
|
||||||
].flatMap((items) => this.filterAndSort(items))
|
].flatMap((items) => this.filterAndSort(items))
|
||||||
|
},
|
||||||
|
get filteredServices() {
|
||||||
|
return this.filterAndSort(this.services)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -180,7 +180,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
||||||
<div class="pb-4 dark:text-warning text-coollabs">If you would like to add a volume, you must add it to
|
<div class="pb-4 dark:text-warning text-coollabs">If you would like to add a volume, you must add it to
|
||||||
your compose file (General tab).</div>
|
your compose file (Service Stack tab).</div>
|
||||||
@foreach ($applications as $application)
|
@foreach ($applications as $application)
|
||||||
<livewire:project.service.storage wire:key="application-{{ $application->id }}" :resource="$application"
|
<livewire:project.service.storage wire:key="application-{{ $application->id }}" :resource="$application"
|
||||||
lazy />
|
lazy />
|
||||||
|
@@ -110,8 +110,7 @@
|
|||||||
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
||||||
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" x-model="search"
|
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" x-model="search"
|
||||||
@focus="open = true" @click.away="open = false" @input="open = true"
|
@focus="open = true" @click.away="open = false" @input="open = true"
|
||||||
class="w-full input" :placeholder="placeholder"
|
class="w-full input" :placeholder="placeholder" wire:model="serverTimezone">
|
||||||
wire:model.debounce.300ms="serverTimezone">
|
|
||||||
<svg class="absolute right-0 mr-2 w-4 h-4" xmlns="http://www.w3.org/2000/svg"
|
<svg class="absolute right-0 mr-2 w-4 h-4" xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||||
@click="open = true">
|
@click="open = true">
|
||||||
@@ -124,7 +123,7 @@
|
|||||||
<template
|
<template
|
||||||
x-for="timezone in timezones.filter(tz => tz.toLowerCase().includes(search.toLowerCase()))"
|
x-for="timezone in timezones.filter(tz => tz.toLowerCase().includes(search.toLowerCase()))"
|
||||||
:key="timezone">
|
:key="timezone">
|
||||||
<div @click="search = timezone; open = false; $wire.set('serverTimezone', timezone)"
|
<div @click="search = timezone; open = false; $wire.set('serverTimezone', timezone); $wire.submit()"
|
||||||
class="px-4 py-2 text-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-coolgray-300 dark:text-gray-200"
|
class="px-4 py-2 text-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-coolgray-300 dark:text-gray-200"
|
||||||
x-text="timezone"></div>
|
x-text="timezone"></div>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -86,16 +86,24 @@
|
|||||||
</g>
|
</g>
|
||||||
</svg></div>
|
</svg></div>
|
||||||
@isset($docker_version)
|
@isset($docker_version)
|
||||||
|
@if($docker_version)
|
||||||
<div class="flex w-64 gap-2">Minimum Docker version: <svg class="w-5 h-5 text-success"
|
<div class="flex w-64 gap-2">Minimum Docker version: <svg class="w-5 h-5 text-success"
|
||||||
|
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
|
||||||
|
opacity=".2" />
|
||||||
|
<path
|
||||||
|
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
|
||||||
|
</g>
|
||||||
|
</svg></div>
|
||||||
|
@else
|
||||||
|
<div class="flex w-64 gap-2">Minimum Docker version: <svg class="w-5 h-5 text-error"
|
||||||
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g fill="currentColor">
|
<path fill="currentColor"
|
||||||
<path
|
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
|
||||||
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
|
|
||||||
opacity=".2" />
|
|
||||||
<path
|
|
||||||
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
|
|
||||||
</g>
|
|
||||||
</svg></div>
|
</svg></div>
|
||||||
|
@endif
|
||||||
@else
|
@else
|
||||||
<div class="w-64"><x-loading text="Minimum Docker version:" /></div>
|
<div class="w-64"><x-loading text="Minimum Docker version:" /></div>
|
||||||
@endisset
|
@endisset
|
||||||
|
@@ -40,14 +40,13 @@
|
|||||||
helper="Timezone for the Coolify instance. This is used for the update check and automatic update frequency." />
|
helper="Timezone for the Coolify instance. This is used for the update check and automatic update frequency." />
|
||||||
</div>
|
</div>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="inline-flex items-center relative w-full">
|
<div class="inline-flex relative items-center w-full">
|
||||||
<input autocomplete="off"
|
<input autocomplete="off"
|
||||||
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
||||||
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" x-model="search"
|
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" x-model="search"
|
||||||
@focus="open = true" @click.away="open = false" @input="open = true"
|
@focus="open = true" @click.away="open = false" @input="open = true"
|
||||||
class="w-full input " :placeholder="placeholder"
|
class="w-full input" :placeholder="placeholder" wire:model="instance_timezone">
|
||||||
wire:model.debounce.300ms="instance_timezone">
|
<svg class="absolute right-0 mr-2 w-4 h-4" xmlns="http://www.w3.org/2000/svg"
|
||||||
<svg class="absolute right-0 w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||||
@click="open = true">
|
@click="open = true">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
@@ -55,26 +54,25 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div x-show="open"
|
<div x-show="open"
|
||||||
class="absolute z-50 w-full mt-1 bg-white dark:bg-coolgray-100 border dark:border-coolgray-200 rounded-md shadow-lg max-h-60 overflow-auto scrollbar overflow-x-hidden">
|
class="overflow-auto overflow-x-hidden absolute z-50 mt-1 w-full max-h-60 bg-white rounded-md border shadow-lg dark:bg-coolgray-100 dark:border-coolgray-200 scrollbar">
|
||||||
<template
|
<template
|
||||||
x-for="timezone in timezones.filter(tz => tz.toLowerCase().includes(search.toLowerCase()))"
|
x-for="timezone in timezones.filter(tz => tz.toLowerCase().includes(search.toLowerCase()))"
|
||||||
:key="timezone">
|
:key="timezone">
|
||||||
<div @click="search = timezone; open = false; $wire.set('instance_timezone', timezone)"
|
<div @click="search = timezone; open = false; $wire.set('instance_timezone', timezone); $wire.submit()"
|
||||||
class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-coolgray-300 text-gray-800 dark:text-gray-200"
|
class="px-4 py-2 text-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-coolgray-300 dark:text-gray-200"
|
||||||
x-text="timezone"></div>
|
x-text="timezone"></div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2 md:flex-row flex-col w-full">
|
||||||
<x-forms.input id="public_ipv4" type="password" label="Instance's IPv4"
|
<x-forms.input id="public_ipv4" type="password" label="Instance's IPv4"
|
||||||
helper="Enter the IPv4 address of the instance.<br><br>It is useful if you have several IPv4 addresses and Coolify could not detect the correct one."
|
helper="Enter the IPv4 address of the instance.<br><br>It is useful if you have several IPv4 addresses and Coolify could not detect the correct one."
|
||||||
placeholder="1.2.3.4" />
|
placeholder="1.2.3.4" autocomplete="new-password" />
|
||||||
<x-forms.input id="public_ipv6" type="password" label="Instance's IPv6"
|
<x-forms.input id="public_ipv6" type="password" label="Instance's IPv6"
|
||||||
helper="Enter the IPv6 address of the instance.<br><br>It is useful if you have several IPv6 addresses and Coolify could not detect the correct one."
|
helper="Enter the IPv6 address of the instance.<br><br>It is useful if you have several IPv6 addresses and Coolify could not detect the correct one."
|
||||||
placeholder="2001:db8::1" />
|
placeholder="2001:db8::1" autocomplete="new-password" />
|
||||||
</div>
|
</div>
|
||||||
<h4 class="w-full pt-6">DNS Validation</h4>
|
<h4 class="w-full pt-6">DNS Validation</h4>
|
||||||
<div class="md:w-96">
|
<div class="md:w-96">
|
||||||
@@ -92,6 +90,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<h4 class="pt-6">API</h4>
|
<h4 class="pt-6">API</h4>
|
||||||
|
<div class="pb-4">For API documentation, please visit <a class="dark:text-warning underline"
|
||||||
|
href="/docs/api">/docs/api</a></div>
|
||||||
<div class="md:w-96 pb-2">
|
<div class="md:w-96 pb-2">
|
||||||
<x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" />
|
<x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" />
|
||||||
</div>
|
</div>
|
||||||
@@ -131,9 +131,13 @@
|
|||||||
|
|
||||||
<h4 class="py-4">Confirmation Settings</h4>
|
<h4 class="py-4">Confirmation Settings</h4>
|
||||||
<div x-data="{ open: false }" class="mb-32 md:w-[40rem]">
|
<div x-data="{ open: false }" class="mb-32 md:w-[40rem]">
|
||||||
<button type="button" @click.prevent="open = !open" class="flex items-center justify-between w-full p-4 bg-coolgray-100 hover:bg-coolgray-200 rounded-md">
|
<button type="button" @click.prevent="open = !open"
|
||||||
|
class="flex items-center justify-between w-full p-4 rounded-md
|
||||||
|
dark:bg-coolgray-100 dark:hover:bg-coolgray-200
|
||||||
|
bg-gray-100 hover:bg-gray-200">
|
||||||
<span class="font-medium">Two-Step Confirmation Settings</span>
|
<span class="font-medium">Two-Step Confirmation Settings</span>
|
||||||
<svg class="w-5 h-5 transition-transform" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5 transition-transform" :class="{ 'rotate-180': open }" fill="none"
|
||||||
|
stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
@@ -141,24 +145,28 @@
|
|||||||
<div x-show="open" x-transition class="mt-4">
|
<div x-show="open" x-transition class="mt-4">
|
||||||
@if ($disable_two_step_confirmation)
|
@if ($disable_two_step_confirmation)
|
||||||
<div class="md:w-96 pb-4">
|
<div class="md:w-96 pb-4">
|
||||||
<x-forms.checkbox instantSave id="disable_two_step_confirmation" label="Disable Two Step Confirmation"
|
<x-forms.checkbox instantSave id="disable_two_step_confirmation"
|
||||||
|
label="Disable Two Step Confirmation"
|
||||||
helper="When disabled, you will not need to confirm actions with a text and user password. This significantly reduces security and may lead to accidental deletions or unwanted changes. Use with extreme caution, especially on production servers." />
|
helper="When disabled, you will not need to confirm actions with a text and user password. This significantly reduces security and may lead to accidental deletions or unwanted changes. Use with extreme caution, especially on production servers." />
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="md:w-96 pb-4">
|
<div class="md:w-96 pb-4">
|
||||||
<x-modal-confirmation title="Disable Two Step Confirmation?"
|
<x-modal-confirmation title="Disable Two Step Confirmation?"
|
||||||
buttonTitle="Disable Two Step Confirmation" isErrorButton submitAction="toggleTwoStepConfirmation"
|
buttonTitle="Disable Two Step Confirmation" isErrorButton
|
||||||
:actions="[
|
submitAction="toggleTwoStepConfirmation" :actions="[
|
||||||
'Tow Step confimation will be disabled globally.',
|
'Tow Step confimation will be disabled globally.',
|
||||||
'Disabling two step confirmation reduces security (as anyone can easily delete anything).',
|
'Disabling two step confirmation reduces security (as anyone can easily delete anything).',
|
||||||
'The risk of accidental actions will increase.',
|
'The risk of accidental actions will increase.',
|
||||||
]" confirmationText="DISABLE TWO STEP CONFIRMATION"
|
]"
|
||||||
|
confirmationText="DISABLE TWO STEP CONFIRMATION"
|
||||||
confirmationLabel="Please type the confirmation text to disable two step confirmation."
|
confirmationLabel="Please type the confirmation text to disable two step confirmation."
|
||||||
shortConfirmationLabel="Confirmation text" step3ButtonText="Disable Two Step Confirmation" />
|
shortConfirmationLabel="Confirmation text"
|
||||||
|
step3ButtonText="Disable Two Step Confirmation" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full px-4 py-2 mb-4 text-white rounded-sm border-l-4 border-red-500 bg-error">
|
<div class="w-full px-4 py-2 mb-4 text-white rounded-sm border-l-4 border-red-500 bg-error">
|
||||||
<p class="font-bold">Warning!</p>
|
<p class="font-bold">Warning!</p>
|
||||||
<p>Disabling two step confirmation reduces security (as anyone can easily delete anything) and increases
|
<p>Disabling two step confirmation reduces security (as anyone can easily delete anything) and
|
||||||
|
increases
|
||||||
the risk of accidental actions. This is not recommended for production servers.</p>
|
the risk of accidental actions. This is not recommended for production servers.</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
<div class="mb-4">For more details, please visit the <a class="underline dark:text-warning"
|
||||||
|
href="https://coolify.io/docs/knowledge-base/s3" target="_blank">Coolify Docs</a>.</div>
|
||||||
<form class="flex flex-col gap-2" wire:submit='submit'>
|
<form class="flex flex-col gap-2" wire:submit='submit'>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input required label="Name" id="name" />
|
<x-forms.input required label="Name" id="name" />
|
||||||
@@ -7,14 +9,15 @@
|
|||||||
<x-forms.input required type="url" label="Endpoint" wire:model.blur="endpoint" />
|
<x-forms.input required type="url" label="Endpoint" wire:model.blur="endpoint" />
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input required label="Bucket" id="bucket" />
|
<x-forms.input required label="Bucket" id="bucket" />
|
||||||
<x-forms.input required label="Region" id="region" />
|
<x-forms.input required helper="Region only required for AWS. Leave it as-is for other providers."
|
||||||
|
label="Region" id="region" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input required type="password" label="Access Key" id="key" />
|
<x-forms.input required type="password" label="Access Key" id="key" />
|
||||||
<x-forms.input required type="password" label="Secret Key" id="secret" />
|
<x-forms.input required type="password" label="Secret Key" id="secret" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-forms.button type="submit">
|
<x-forms.button class="mt-4" type="submit">
|
||||||
Validate Connection & Continue
|
Validate Connection & Continue
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (!$storage->is_usable)
|
@if (!$storage->is_usable)
|
||||||
<div class="text-red-500">Not Usable</div>
|
<div class="text-red-500">Not Usable</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
|
@@ -8,27 +8,32 @@
|
|||||||
<x-helper
|
<x-helper
|
||||||
helper="If you're having trouble connecting to your server, make sure that the port is open.<br><br><a class='underline' href='https://coolify.io/docs/knowledge-base/server/firewall/#terminal' target='_blank'>Documentation</a>"></x-helper>
|
helper="If you're having trouble connecting to your server, make sure that the port is open.<br><br><a class='underline' href='https://coolify.io/docs/knowledge-base/server/firewall/#terminal' target='_blank'>Documentation</a>"></x-helper>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div x-init="$wire.loadContainers()">
|
||||||
<form class="flex flex-col gap-2 justify-center xl:items-end xl:flex-row"
|
@if ($isLoadingContainers)
|
||||||
wire:submit="$dispatchSelf('connectToContainer')">
|
<div class="pt-1">
|
||||||
<x-forms.select id="server" required wire:model.live="selected_uuid">
|
<x-loading text="Loading servers and containers..." />
|
||||||
@foreach ($servers as $server)
|
</div>
|
||||||
@if ($loop->first)
|
@else
|
||||||
<option disabled value="default">Select a server or container</option>
|
<form class="flex flex-col gap-2 justify-center xl:items-end xl:flex-row"
|
||||||
@endif
|
wire:submit="$dispatchSelf('connectToContainer')">
|
||||||
<option value="{{ $server->uuid }}">{{ $server->name }}</option>
|
<x-forms.select id="server" required wire:model.live="selected_uuid">
|
||||||
@foreach ($containers as $container)
|
@foreach ($servers as $server)
|
||||||
@if ($container['server_uuid'] == $server->uuid)
|
@if ($loop->first)
|
||||||
<option value="{{ $container['uuid'] }}">
|
<option disabled value="default">Select a server or container</option>
|
||||||
{{ $server->name }} -> {{ $container['name'] }}
|
|
||||||
</option>
|
|
||||||
@endif
|
@endif
|
||||||
|
<option value="{{ $server->uuid }}">{{ $server->name }}</option>
|
||||||
|
@foreach ($containers as $container)
|
||||||
|
@if ($container['server_uuid'] == $server->uuid)
|
||||||
|
<option value="{{ $container['uuid'] }}">
|
||||||
|
{{ $server->name }} -> {{ $container['name'] }}
|
||||||
|
</option>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
@endforeach
|
@endforeach
|
||||||
@endforeach
|
</x-forms.select>
|
||||||
</x-forms.select>
|
<x-forms.button type="submit">Connect</x-forms.button>
|
||||||
<x-forms.button type="submit">Connect</x-forms.button>
|
</form>
|
||||||
</form>
|
<livewire:project.shared.terminal />
|
||||||
<livewire:project.shared.terminal />
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -4,6 +4,7 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Http\Controllers\MagicController;
|
use App\Http\Controllers\MagicController;
|
||||||
use App\Http\Controllers\OauthController;
|
use App\Http\Controllers\OauthController;
|
||||||
use App\Http\Controllers\UploadController;
|
use App\Http\Controllers\UploadController;
|
||||||
|
use App\Http\Middleware\ApiAllowed;
|
||||||
use App\Livewire\Admin\Index as AdminIndex;
|
use App\Livewire\Admin\Index as AdminIndex;
|
||||||
use App\Livewire\Boarding\Index as BoardingIndex;
|
use App\Livewire\Boarding\Index as BoardingIndex;
|
||||||
use App\Livewire\Dashboard;
|
use App\Livewire\Dashboard;
|
||||||
@@ -72,13 +73,18 @@ use App\Livewire\Team\Member\Index as TeamMemberIndex;
|
|||||||
use App\Livewire\Terminal\Index as TerminalIndex;
|
use App\Livewire\Terminal\Index as TerminalIndex;
|
||||||
use App\Models\GitlabApp;
|
use App\Models\GitlabApp;
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
use App\Models\ScheduledDatabaseBackupExecution;
|
||||||
use App\Models\Server;
|
|
||||||
use App\Providers\RouteServiceProvider;
|
use App\Providers\RouteServiceProvider;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
use ThreeSidedCube\LaravelRedoc\Http\Controllers\DefinitionController;
|
||||||
|
use ThreeSidedCube\LaravelRedoc\Http\Controllers\DocumentationController;
|
||||||
|
|
||||||
|
Route::group(['middleware' => ['auth:sanctum', ApiAllowed::class]], function () {
|
||||||
|
Route::get('/docs/api', DocumentationController::class)->name('redoc.documentation');
|
||||||
|
Route::get('/docs/api/definition', DefinitionController::class)->name('redoc.definition');
|
||||||
|
});
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
Route::get('/dev/compose', Compose::class)->name('dev.compose');
|
Route::get('/dev/compose', Compose::class)->name('dev.compose');
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ function logs {
|
|||||||
docker exec -t coolify tail -f storage/logs/laravel.log
|
docker exec -t coolify tail -f storage/logs/laravel.log
|
||||||
}
|
}
|
||||||
function test {
|
function test {
|
||||||
docker exec -t coolify php artisan test --testsuite=Feature
|
docker exec -t coolify php artisan test --testsuite=Feature -p
|
||||||
}
|
}
|
||||||
|
|
||||||
function sync:bunny {
|
function sync:bunny {
|
||||||
|
62
tests/Feature/ConvertingGitUrlsTest.php
Normal file
62
tests/Feature/ConvertingGitUrlsTest.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\GithubApp;
|
||||||
|
|
||||||
|
test('convertGitUrlsForDeployKeyAndGithubAppAndHttpUrl', function () {
|
||||||
|
$githubApp = GithubApp::find(0);
|
||||||
|
$result = convertGitUrl('andrasbacsai/coolify-examples.git', 'deploy_key', $githubApp);
|
||||||
|
expect($result)->toBe([
|
||||||
|
'repository' => 'git@github.com:andrasbacsai/coolify-examples.git',
|
||||||
|
'port' => 22,
|
||||||
|
]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('convertGitUrlsForDeployKeyAndGithubAppAndSshUrl', function () {
|
||||||
|
$githubApp = GithubApp::find(0);
|
||||||
|
$result = convertGitUrl('git@github.com:andrasbacsai/coolify-examples.git', 'deploy_key', $githubApp);
|
||||||
|
expect($result)->toBe([
|
||||||
|
'repository' => 'git@github.com:andrasbacsai/coolify-examples.git',
|
||||||
|
'port' => 22,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('convertGitUrlsForDeployKeyAndHttpUrl', function () {
|
||||||
|
$result = convertGitUrl('andrasbacsai/coolify-examples.git', 'deploy_key', null);
|
||||||
|
expect($result)->toBe([
|
||||||
|
'repository' => 'andrasbacsai/coolify-examples.git',
|
||||||
|
'port' => 22,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('convertGitUrlsForDeployKeyAndSshUrl', function () {
|
||||||
|
$result = convertGitUrl('git@github.com:andrasbacsai/coolify-examples.git', 'deploy_key', null);
|
||||||
|
expect($result)->toBe([
|
||||||
|
'repository' => 'git@github.com:andrasbacsai/coolify-examples.git',
|
||||||
|
'port' => 22,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('convertGitUrlsForSourceAndSshUrl', function () {
|
||||||
|
$result = convertGitUrl('git@github.com:andrasbacsai/coolify-examples.git', 'source', null);
|
||||||
|
expect($result)->toBe([
|
||||||
|
'repository' => 'git@github.com:andrasbacsai/coolify-examples.git',
|
||||||
|
'port' => 22,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('convertGitUrlsForSourceAndHttpUrl', function () {
|
||||||
|
$result = convertGitUrl('andrasbacsai/coolify-examples.git', 'source', null);
|
||||||
|
expect($result)->toBe([
|
||||||
|
'repository' => 'andrasbacsai/coolify-examples.git',
|
||||||
|
'port' => 22,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('convertGitUrlsForSourceAndSshUrlWithCustomPort', function () {
|
||||||
|
$result = convertGitUrl('git@git.domain.com:766/group/project.git', 'source', null);
|
||||||
|
expect($result)->toBe([
|
||||||
|
'repository' => 'git@git.domain.com:group/project.git',
|
||||||
|
'port' => '766',
|
||||||
|
]);
|
||||||
|
});
|
@@ -9,171 +9,171 @@ use App\Models\StandaloneDocker;
|
|||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
beforeEach(function () {
|
// beforeEach(function () {
|
||||||
$this->applicationYaml = '
|
// $this->applicationYaml = '
|
||||||
version: "3.8"
|
// version: "3.8"
|
||||||
services:
|
// services:
|
||||||
app:
|
// app:
|
||||||
image: nginx
|
// image: nginx
|
||||||
environment:
|
// environment:
|
||||||
SERVICE_FQDN_APP: /app
|
// SERVICE_FQDN_APP: /app
|
||||||
APP_KEY: base64
|
// APP_KEY: base64
|
||||||
APP_DEBUG: "${APP_DEBUG:-false}"
|
// APP_DEBUG: "${APP_DEBUG:-false}"
|
||||||
APP_URL: $SERVICE_FQDN_APP
|
// APP_URL: $SERVICE_FQDN_APP
|
||||||
DB_URL: postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@db:5432/postgres?schema=public
|
// DB_URL: postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@db:5432/postgres?schema=public
|
||||||
volumes:
|
// volumes:
|
||||||
- "./nginx:/etc/nginx"
|
// - "./nginx:/etc/nginx"
|
||||||
- "data:/var/www/html"
|
// - "data:/var/www/html"
|
||||||
depends_on:
|
// depends_on:
|
||||||
- db
|
// - db
|
||||||
db:
|
// db:
|
||||||
image: postgres
|
// image: postgres
|
||||||
environment:
|
// environment:
|
||||||
POSTGRES_USER: "${SERVICE_USER_POSTGRES}"
|
// POSTGRES_USER: "${SERVICE_USER_POSTGRES}"
|
||||||
POSTGRES_PASSWORD: "${SERVICE_PASSWORD_POSTGRES}"
|
// POSTGRES_PASSWORD: "${SERVICE_PASSWORD_POSTGRES}"
|
||||||
volumes:
|
// volumes:
|
||||||
- "dbdata:/var/lib/postgresql/data"
|
// - "dbdata:/var/lib/postgresql/data"
|
||||||
healthcheck:
|
// healthcheck:
|
||||||
test:
|
// test:
|
||||||
- CMD
|
// - CMD
|
||||||
- pg_isready
|
// - pg_isready
|
||||||
- "-U"
|
// - "-U"
|
||||||
- "postgres"
|
// - "postgres"
|
||||||
interval: 2s
|
// interval: 2s
|
||||||
timeout: 10s
|
// timeout: 10s
|
||||||
retries: 10
|
// retries: 10
|
||||||
depends_on:
|
// depends_on:
|
||||||
app:
|
// app:
|
||||||
condition: service_healthy
|
// condition: service_healthy
|
||||||
networks:
|
// networks:
|
||||||
default:
|
// default:
|
||||||
name: something
|
// name: something
|
||||||
external: true
|
// external: true
|
||||||
noinet:
|
// noinet:
|
||||||
driver: bridge
|
// driver: bridge
|
||||||
internal: true';
|
// internal: true';
|
||||||
|
|
||||||
$this->applicationComposeFileString = Yaml::parse($this->applicationYaml);
|
// $this->applicationComposeFileString = Yaml::parse($this->applicationYaml);
|
||||||
|
|
||||||
$this->application = Application::create([
|
// $this->application = Application::create([
|
||||||
'name' => 'Application for tests',
|
// 'name' => 'Application for tests',
|
||||||
'docker_compose_domains' => json_encode([
|
// 'docker_compose_domains' => json_encode([
|
||||||
'app' => [
|
// 'app' => [
|
||||||
'domain' => 'http://bcoowoookw0co4cok4sgc4k8.127.0.0.1.sslip.io',
|
// 'domain' => 'http://bcoowoookw0co4cok4sgc4k8.127.0.0.1.sslip.io',
|
||||||
],
|
// ],
|
||||||
]),
|
// ]),
|
||||||
'preview_url_template' => '{{pr_id}}.{{domain}}',
|
// 'preview_url_template' => '{{pr_id}}.{{domain}}',
|
||||||
'uuid' => 'bcoowoookw0co4cok4sgc4k8s',
|
// 'uuid' => 'bcoowoookw0co4cok4sgc4k8s',
|
||||||
'repository_project_id' => 603035348,
|
// 'repository_project_id' => 603035348,
|
||||||
'git_repository' => 'coollabsio/coolify-examples',
|
// 'git_repository' => 'coollabsio/coolify-examples',
|
||||||
'git_branch' => 'main',
|
// 'git_branch' => 'main',
|
||||||
'base_directory' => '/docker-compose-test',
|
// 'base_directory' => '/docker-compose-test',
|
||||||
'docker_compose_location' => 'docker-compose.yml',
|
// 'docker_compose_location' => 'docker-compose.yml',
|
||||||
'docker_compose_raw' => $this->applicationYaml,
|
// 'docker_compose_raw' => $this->applicationYaml,
|
||||||
'build_pack' => 'dockercompose',
|
// 'build_pack' => 'dockercompose',
|
||||||
'ports_exposes' => '3000',
|
// 'ports_exposes' => '3000',
|
||||||
'environment_id' => 1,
|
// 'environment_id' => 1,
|
||||||
'destination_id' => 0,
|
// 'destination_id' => 0,
|
||||||
'destination_type' => StandaloneDocker::class,
|
// 'destination_type' => StandaloneDocker::class,
|
||||||
'source_id' => 1,
|
// 'source_id' => 1,
|
||||||
'source_type' => GithubApp::class,
|
// 'source_type' => GithubApp::class,
|
||||||
]);
|
// ]);
|
||||||
$this->application->environment_variables_preview()->where('key', 'APP_DEBUG')->update(['value' => 'true']);
|
// $this->application->environment_variables_preview()->where('key', 'APP_DEBUG')->update(['value' => 'true']);
|
||||||
$this->applicationPreview = ApplicationPreview::create([
|
// $this->applicationPreview = ApplicationPreview::create([
|
||||||
'git_type' => 'github',
|
// 'git_type' => 'github',
|
||||||
'application_id' => $this->application->id,
|
// 'application_id' => $this->application->id,
|
||||||
'pull_request_id' => 1,
|
// 'pull_request_id' => 1,
|
||||||
'pull_request_html_url' => 'https://github.com/coollabsio/coolify-examples/pull/1',
|
// 'pull_request_html_url' => 'https://github.com/coollabsio/coolify-examples/pull/1',
|
||||||
]);
|
// ]);
|
||||||
$this->serviceYaml = '
|
// $this->serviceYaml = '
|
||||||
services:
|
// services:
|
||||||
activepieces:
|
// activepieces:
|
||||||
image: "ghcr.io/activepieces/activepieces:latest"
|
// image: "ghcr.io/activepieces/activepieces:latest"
|
||||||
environment:
|
// environment:
|
||||||
- SERVICE_FQDN_ACTIVEPIECES
|
// - SERVICE_FQDN_ACTIVEPIECES
|
||||||
- AP_API_KEY=$SERVICE_PASSWORD_64_APIKEY
|
// - AP_API_KEY=$SERVICE_PASSWORD_64_APIKEY
|
||||||
- AP_URL=$SERVICE_URL_ACTIVEPIECES
|
// - AP_URL=$SERVICE_URL_ACTIVEPIECES
|
||||||
- AP_ENCRYPTION_KEY=$SERVICE_PASSWORD_ENCRYPTIONKEY
|
// - AP_ENCRYPTION_KEY=$SERVICE_PASSWORD_ENCRYPTIONKEY
|
||||||
- AP_ENGINE_EXECUTABLE_PATH=dist/packages/engine/main.js
|
// - AP_ENGINE_EXECUTABLE_PATH=dist/packages/engine/main.js
|
||||||
- AP_ENVIRONMENT=prod
|
// - AP_ENVIRONMENT=prod
|
||||||
- AP_EXECUTION_MODE=${AP_EXECUTION_MODE}
|
// - AP_EXECUTION_MODE=${AP_EXECUTION_MODE}
|
||||||
- AP_FRONTEND_URL=$SERVICE_FQDN_ACTIVEPIECES
|
// - AP_FRONTEND_URL=$SERVICE_FQDN_ACTIVEPIECES
|
||||||
- AP_JWT_SECRET=$SERVICE_PASSWORD_64_JWT
|
// - AP_JWT_SECRET=$SERVICE_PASSWORD_64_JWT
|
||||||
- AP_POSTGRES_DATABASE=activepieces
|
// - AP_POSTGRES_DATABASE=activepieces
|
||||||
- AP_POSTGRES_HOST=postgres
|
// - AP_POSTGRES_HOST=postgres
|
||||||
- AP_POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
// - AP_POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
||||||
- AP_POSTGRES_PORT=5432
|
// - AP_POSTGRES_PORT=5432
|
||||||
- AP_POSTGRES_USERNAME=$SERVICE_USER_POSTGRES
|
// - AP_POSTGRES_USERNAME=$SERVICE_USER_POSTGRES
|
||||||
- AP_REDIS_HOST=redis
|
// - AP_REDIS_HOST=redis
|
||||||
- AP_REDIS_PORT=6379
|
// - AP_REDIS_PORT=6379
|
||||||
- AP_SANDBOX_RUN_TIME_SECONDS=600
|
// - AP_SANDBOX_RUN_TIME_SECONDS=600
|
||||||
- AP_TELEMETRY_ENABLED=true
|
// - AP_TELEMETRY_ENABLED=true
|
||||||
- "AP_TEMPLATES_SOURCE_URL=https://cloud.activepieces.com/api/v1/flow-templates"
|
// - "AP_TEMPLATES_SOURCE_URL=https://cloud.activepieces.com/api/v1/flow-templates"
|
||||||
- AP_TRIGGER_DEFAULT_POLL_INTERVAL=5
|
// - AP_TRIGGER_DEFAULT_POLL_INTERVAL=5
|
||||||
- AP_WEBHOOK_TIMEOUT_SECONDS=30
|
// - AP_WEBHOOK_TIMEOUT_SECONDS=30
|
||||||
depends_on:
|
// depends_on:
|
||||||
postgres:
|
// postgres:
|
||||||
condition: service_healthy
|
// condition: service_healthy
|
||||||
redis:
|
// redis:
|
||||||
condition: service_started
|
// condition: service_started
|
||||||
healthcheck:
|
// healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:80"]
|
// test: ["CMD", "curl", "-f", "http://127.0.0.1:80"]
|
||||||
interval: 5s
|
// interval: 5s
|
||||||
timeout: 20s
|
// timeout: 20s
|
||||||
retries: 10
|
// retries: 10
|
||||||
postgres:
|
// postgres:
|
||||||
image: "nginx"
|
// image: "nginx"
|
||||||
environment:
|
// environment:
|
||||||
- SERVICE_FQDN_ACTIVEPIECES=/api
|
// - SERVICE_FQDN_ACTIVEPIECES=/api
|
||||||
- POSTGRES_DB=activepieces
|
// - POSTGRES_DB=activepieces
|
||||||
- PASSW=$AP_POSTGRES_PASSWORD
|
// - PASSW=$AP_POSTGRES_PASSWORD
|
||||||
- AP_FRONTEND_URL=$SERVICE_FQDN_ACTIVEPIECES
|
// - AP_FRONTEND_URL=$SERVICE_FQDN_ACTIVEPIECES
|
||||||
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
// - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
||||||
- POSTGRES_USER=$SERVICE_USER_POSTGRES
|
// - POSTGRES_USER=$SERVICE_USER_POSTGRES
|
||||||
volumes:
|
// volumes:
|
||||||
- "pg-data:/var/lib/postgresql/data"
|
// - "pg-data:/var/lib/postgresql/data"
|
||||||
healthcheck:
|
// healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
// test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||||
interval: 5s
|
// interval: 5s
|
||||||
timeout: 20s
|
// timeout: 20s
|
||||||
retries: 10
|
// retries: 10
|
||||||
redis:
|
// redis:
|
||||||
image: "redis:latest"
|
// image: "redis:latest"
|
||||||
volumes:
|
// volumes:
|
||||||
- "redis_data:/data"
|
// - "redis_data:/data"
|
||||||
healthcheck:
|
// healthcheck:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
// test: ["CMD", "redis-cli", "ping"]
|
||||||
interval: 5s
|
// interval: 5s
|
||||||
timeout: 20s
|
// timeout: 20s
|
||||||
retries: 10
|
// retries: 10
|
||||||
|
|
||||||
';
|
// ';
|
||||||
|
|
||||||
$this->serviceComposeFileString = Yaml::parse($this->serviceYaml);
|
// $this->serviceComposeFileString = Yaml::parse($this->serviceYaml);
|
||||||
|
|
||||||
$this->service = Service::create([
|
// $this->service = Service::create([
|
||||||
'name' => 'Service for tests',
|
// 'name' => 'Service for tests',
|
||||||
'uuid' => 'tgwcg8w4s844wkog8kskw44g',
|
// 'uuid' => 'tgwcg8w4s844wkog8kskw44g',
|
||||||
'docker_compose_raw' => $this->serviceYaml,
|
// 'docker_compose_raw' => $this->serviceYaml,
|
||||||
'environment_id' => 1,
|
// 'environment_id' => 1,
|
||||||
'server_id' => 0,
|
// 'server_id' => 0,
|
||||||
'destination_id' => 0,
|
// 'destination_id' => 0,
|
||||||
'destination_type' => StandaloneDocker::class,
|
// 'destination_type' => StandaloneDocker::class,
|
||||||
]);
|
// ]);
|
||||||
});
|
// });
|
||||||
|
|
||||||
afterEach(function () {
|
// afterEach(function () {
|
||||||
// $this->applicationPreview->forceDelete();
|
// // $this->applicationPreview->forceDelete();
|
||||||
$this->application->forceDelete();
|
// $this->application->forceDelete();
|
||||||
DeleteResourceJob::dispatchSync($this->service);
|
// DeleteResourceJob::dispatchSync($this->service);
|
||||||
$this->service->forceDelete();
|
// $this->service->forceDelete();
|
||||||
});
|
// });
|
||||||
|
|
||||||
test('ServiceComposeParseNew', function () {
|
// test('ServiceComposeParseNew', function () {
|
||||||
$output = newParser($this->service);
|
// $output = newParser($this->service);
|
||||||
$this->service->saveComposeConfigs();
|
// $this->service->saveComposeConfigs();
|
||||||
expect($output)->toBeInstanceOf(Collection::class);
|
// expect($output)->toBeInstanceOf(Collection::class);
|
||||||
});
|
// });
|
||||||
|
|
||||||
// test('ApplicationComposeParse', function () {
|
// test('ApplicationComposeParse', function () {
|
||||||
// expect($this->jsonapplicationComposeFile)->toBeJson()->ray();
|
// expect($this->jsonapplicationComposeFile)->toBeJson()->ray();
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.367"
|
"version": "4.0.0-beta.368"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.368"
|
"version": "4.0.0-beta.369"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.3"
|
"version": "1.0.3"
|
||||||
|
Reference in New Issue
Block a user