testing php storm code cleanup and styling
This commit is contained in:
parent
a8ee779b31
commit
f2228cec7b
@ -15,6 +15,7 @@ class PrepareCoolifyTask
|
||||
{
|
||||
protected Activity $activity;
|
||||
protected CoolifyTaskArgs $remoteProcessArgs;
|
||||
|
||||
public function __construct(CoolifyTaskArgs $remoteProcessArgs)
|
||||
{
|
||||
$this->remoteProcessArgs = $remoteProcessArgs;
|
||||
|
@ -49,6 +49,28 @@ public function __construct(Activity $activity, bool $hide_from_output = false,
|
||||
$this->ignore_errors = $ignore_errors;
|
||||
}
|
||||
|
||||
public static function decodeOutput(?Activity $activity = null): string
|
||||
{
|
||||
if (is_null($activity)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
$decoded = json_decode(
|
||||
data_get($activity, 'description'),
|
||||
associative: true,
|
||||
flags: JSON_THROW_ON_ERROR
|
||||
);
|
||||
} catch (\JsonException $exception) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return collect($decoded)
|
||||
->sortBy(fn($i) => $i['order'])
|
||||
->map(fn($i) => $i['output'])
|
||||
->implode("");
|
||||
}
|
||||
|
||||
public function __invoke(): ProcessResult
|
||||
{
|
||||
$this->time_start = hrtime(true);
|
||||
@ -83,15 +105,6 @@ public function __invoke(): ProcessResult
|
||||
return $processResult;
|
||||
}
|
||||
|
||||
protected function getLatestCounter(): int
|
||||
{
|
||||
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
if ($description === null || count($description) === 0) {
|
||||
return 1;
|
||||
}
|
||||
return end($description)['order'] + 1;
|
||||
}
|
||||
|
||||
protected function getCommand(): string
|
||||
{
|
||||
$user = $this->activity->getExtraProperty('user');
|
||||
@ -120,6 +133,13 @@ protected function handleOutput(string $type, string $output)
|
||||
}
|
||||
}
|
||||
|
||||
protected function elapsedTime(): int
|
||||
{
|
||||
$timeMs = (hrtime(true) - $this->time_start) / 1_000_000;
|
||||
|
||||
return intval($timeMs);
|
||||
}
|
||||
|
||||
public function encodeOutput($type, $output)
|
||||
{
|
||||
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
@ -135,26 +155,13 @@ public function encodeOutput($type, $output)
|
||||
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
public static function decodeOutput(?Activity $activity = null): string
|
||||
protected function getLatestCounter(): int
|
||||
{
|
||||
if (is_null($activity)) {
|
||||
return '';
|
||||
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
if ($description === null || count($description) === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
$decoded = json_decode(
|
||||
data_get($activity, 'description'),
|
||||
associative: true,
|
||||
flags: JSON_THROW_ON_ERROR
|
||||
);
|
||||
} catch (\JsonException $exception) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return collect($decoded)
|
||||
->sortBy(fn ($i) => $i['order'])
|
||||
->map(fn ($i) => $i['output'])
|
||||
->implode("");
|
||||
return end($description)['order'] + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,11 +178,4 @@ protected function isAfterLastThrottle()
|
||||
|
||||
return ($this->current_time - $this->throttle_interval_ms) > $this->last_write_at;
|
||||
}
|
||||
|
||||
protected function elapsedTime(): int
|
||||
{
|
||||
$timeMs = (hrtime(true) - $this->time_start) / 1_000_000;
|
||||
|
||||
return intval($timeMs);
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,14 @@
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class StartPostgresql
|
||||
{
|
||||
public $database;
|
||||
|
||||
public function __invoke(Server $server, StandalonePostgresql $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
@ -83,28 +82,7 @@ public function __invoke(Server $server, StandalonePostgresql $database)
|
||||
], $server);
|
||||
return $activity;
|
||||
}
|
||||
private function generate_environment_variables()
|
||||
{
|
||||
$environment_variables = collect();
|
||||
ray('Generate Environment Variables')->green();
|
||||
ray($this->database->runtime_environment_variables)->green();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
||||
}
|
||||
return $environment_variables->all();
|
||||
}
|
||||
private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
@ -114,6 +92,7 @@ private function generate_local_persistent_volumes()
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes_only_volume_names()
|
||||
{
|
||||
$local_persistent_volumes_names = [];
|
||||
@ -129,4 +108,27 @@ private function generate_local_persistent_volumes_only_volume_names()
|
||||
}
|
||||
return $local_persistent_volumes_names;
|
||||
}
|
||||
|
||||
private function generate_environment_variables()
|
||||
{
|
||||
$environment_variables = collect();
|
||||
ray('Generate Environment Variables')->green();
|
||||
ray($this->database->runtime_environment_variables)->green();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
||||
}
|
||||
return $environment_variables->all();
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,8 @@
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Fortify\Contracts\CreatesNewUsers;
|
||||
@ -19,7 +16,7 @@ class CreateNewUser implements CreatesNewUsers
|
||||
/**
|
||||
* Validate and create a newly registered user.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function create(array $input): User
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ class ResetUserPassword implements ResetsUserPasswords
|
||||
/**
|
||||
* Validate and reset the user's forgotten password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function reset(User $user, array $input): void
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ class UpdateUserPassword implements UpdatesUserPasswords
|
||||
/**
|
||||
* Validate and update the user's password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
||||
/**
|
||||
* Validate and update the given user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
@ -43,7 +43,7 @@ public function update(User $user, array $input): void
|
||||
/**
|
||||
* Update the given verified user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
protected function updateVerifiedUser(User $user, array $input): void
|
||||
{
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Actions\Proxy\SaveConfigurationSync;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
|
@ -2,12 +2,11 @@
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Actions\Proxy\CheckConfigurationSync;
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class StartProxy
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ public function __invoke(bool $force)
|
||||
$this->server = Server::where('name', $localhost_name)->firstOrFail();
|
||||
$this->latest_version = get_latest_version_of_coolify();
|
||||
$this->current_version = config('version');
|
||||
ray('latest version:' . $this->latest_version . " current version: " . $this->current_version . ' force: ' . $force);
|
||||
ray('latest version:' . $this->latest_version . " current version: " . $this->current_version . ' force: ' . $force);
|
||||
if ($settings->next_channel) {
|
||||
ray('next channel enabled');
|
||||
$this->latest_version = 'next';
|
||||
@ -49,6 +49,7 @@ public function __invoke(bool $force)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function update()
|
||||
{
|
||||
if (isDev()) {
|
||||
|
@ -10,10 +10,12 @@ class Init extends Command
|
||||
{
|
||||
protected $signature = 'app:init';
|
||||
protected $description = 'Cleanup instance related stuffs';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
}
|
||||
|
||||
private function cleanup_in_progress_application_deployments()
|
||||
{
|
||||
// Cleanup any failed deployments
|
||||
|
@ -64,7 +64,8 @@ private function showHelp()
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
HTML);
|
||||
HTML
|
||||
);
|
||||
|
||||
ask(<<<'HTML'
|
||||
<div class="mr-1">
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Http\Client\Pool;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class SyncBunny extends Command
|
||||
{
|
||||
@ -64,7 +64,7 @@ public function handle()
|
||||
]);
|
||||
});
|
||||
try {
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
Http::pool(fn(Pool $pool) => [
|
||||
$pool->storage(file: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
||||
$pool->storage(file: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
||||
$pool->storage(file: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
|
||||
@ -73,7 +73,7 @@ public function handle()
|
||||
$pool->storage(file: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
||||
]);
|
||||
ray("{$bunny_cdn}/{$bunny_cdn_path}");
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
Http::pool(fn(Pool $pool) => [
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file_prod"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$production_env"),
|
||||
|
@ -2,12 +2,12 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\InstanceApplicationsStatusJob;
|
||||
use App\Jobs\CheckResaleLicenseJob;
|
||||
use App\Jobs\CheckResaleLicenseKeys;
|
||||
use App\Jobs\DockerCleanupJob;
|
||||
use App\Jobs\InstanceApplicationsStatusJob;
|
||||
use App\Jobs\InstanceAutoUpdateJob;
|
||||
use App\Jobs\ProxyCheckJob;
|
||||
use App\Jobs\DockerCleanupJob;
|
||||
use App\Jobs\CheckResaleLicenseKeys;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
@ -31,6 +31,7 @@ protected function schedule(Schedule $schedule): void
|
||||
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes();
|
||||
}
|
||||
}
|
||||
|
||||
protected function commands(): void
|
||||
{
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
|
@ -22,6 +22,7 @@ public function __construct(
|
||||
public ?Model $model = null,
|
||||
public string $status = ProcessStatus::QUEUED->value,
|
||||
public bool $ignore_errors = false,
|
||||
) {
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,9 @@
|
||||
class ServerMetadata extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public ?ProxyTypes $type,
|
||||
public ?ProxyTypes $type,
|
||||
public ?ProxyStatus $status
|
||||
) {
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ enum ProxyTypes: string
|
||||
case NGINX = 'NGINX';
|
||||
case CADDY = 'CADDY';
|
||||
}
|
||||
|
||||
enum ProxyStatus: string
|
||||
{
|
||||
case EXITED = 'exited';
|
||||
|
@ -4,13 +4,12 @@
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Throwable;
|
||||
use Sentry\Laravel\Integration;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
|
||||
private InstanceSettings $settings;
|
||||
/**
|
||||
* A list of exception types with their corresponding custom log levels.
|
||||
*
|
||||
@ -19,7 +18,6 @@ class Handler extends ExceptionHandler
|
||||
protected $levels = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
@ -28,7 +26,6 @@ class Handler extends ExceptionHandler
|
||||
protected $dontReport = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||
*
|
||||
@ -39,6 +36,7 @@ class Handler extends ExceptionHandler
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
private InstanceSettings $settings;
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
|
@ -5,12 +5,11 @@
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\Request;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class ApplicationController extends Controller
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
|
||||
public function configuration()
|
||||
{
|
||||
$project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
@ -28,6 +27,7 @@ public function configuration()
|
||||
ray($application->persistentStorages()->get());
|
||||
return view('project.application.configuration', ['application' => $application]);
|
||||
}
|
||||
|
||||
public function deployments()
|
||||
{
|
||||
$project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
|
@ -2,17 +2,15 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Livewire\Team\Invitations;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\Server;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Models\S3Storage;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
@ -27,6 +25,7 @@ public function subscription()
|
||||
'settings' => InstanceSettings::get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function license()
|
||||
{
|
||||
if (!isCloud()) {
|
||||
@ -36,6 +35,7 @@ public function license()
|
||||
'settings' => InstanceSettings::get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function dashboard()
|
||||
{
|
||||
$projects = Project::ownedByCurrentTeam()->get();
|
||||
@ -55,6 +55,7 @@ public function dashboard()
|
||||
's3s' => $s3s,
|
||||
]);
|
||||
}
|
||||
|
||||
public function settings()
|
||||
{
|
||||
if (auth()->user()->isInstanceAdmin()) {
|
||||
@ -66,6 +67,7 @@ public function settings()
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
public function emails()
|
||||
{
|
||||
if (auth()->user()->isInstanceAdmin()) {
|
||||
@ -77,6 +79,7 @@ public function emails()
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
public function team()
|
||||
{
|
||||
$invitations = [];
|
||||
@ -87,18 +90,23 @@ public function team()
|
||||
'invitations' => $invitations,
|
||||
]);
|
||||
}
|
||||
public function storages() {
|
||||
|
||||
public function storages()
|
||||
{
|
||||
$s3 = S3Storage::ownedByCurrentTeam()->get();
|
||||
return view('team.storages.all', [
|
||||
's3' => $s3,
|
||||
]);
|
||||
}
|
||||
public function storages_show() {
|
||||
|
||||
public function storages_show()
|
||||
{
|
||||
$storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->firstOrFail();
|
||||
return view('team.storages.show', [
|
||||
'storage' => $storage,
|
||||
]);
|
||||
}
|
||||
|
||||
public function members()
|
||||
{
|
||||
$invitations = [];
|
||||
@ -109,6 +117,7 @@ public function members()
|
||||
'invitations' => $invitations,
|
||||
]);
|
||||
}
|
||||
|
||||
public function acceptInvitation()
|
||||
{
|
||||
try {
|
||||
@ -135,6 +144,7 @@ public function acceptInvitation()
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
public function revokeInvitation()
|
||||
{
|
||||
try {
|
||||
|
@ -2,15 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\Request;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class DatabaseController extends Controller
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
|
||||
public function configuration()
|
||||
{
|
||||
$project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Livewire\Server\PrivateKey;
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
@ -16,24 +15,28 @@ public function servers()
|
||||
'servers' => Server::isUsable()->get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function destinations()
|
||||
{
|
||||
return response()->json([
|
||||
'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name')
|
||||
]);
|
||||
}
|
||||
|
||||
public function projects()
|
||||
{
|
||||
return response()->json([
|
||||
'projects' => Project::ownedByCurrentTeam()->get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function environments()
|
||||
{
|
||||
return response()->json([
|
||||
'environments' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first()->environments
|
||||
]);
|
||||
}
|
||||
|
||||
public function newProject()
|
||||
{
|
||||
$project = Project::firstOrCreate(
|
||||
@ -44,6 +47,7 @@ public function newProject()
|
||||
'project_uuid' => $project->uuid
|
||||
]);
|
||||
}
|
||||
|
||||
public function newEnvironment()
|
||||
{
|
||||
$environment = Environment::firstOrCreate(
|
||||
@ -54,6 +58,7 @@ public function newEnvironment()
|
||||
'environment_name' => $environment->name,
|
||||
]);
|
||||
}
|
||||
|
||||
public function newTeam()
|
||||
{
|
||||
$team = Team::create(
|
||||
|
@ -25,6 +25,7 @@ public function edit()
|
||||
}
|
||||
return view('project.edit', ['project' => $project]);
|
||||
}
|
||||
|
||||
public function show()
|
||||
{
|
||||
$projectUuid = request()->route('project_uuid');
|
||||
@ -55,6 +56,7 @@ public function new()
|
||||
'type' => $type
|
||||
]);
|
||||
}
|
||||
|
||||
public function resources()
|
||||
{
|
||||
$project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
|
@ -15,12 +15,6 @@ class ActivityMonitor extends Component
|
||||
protected $activity;
|
||||
protected $listeners = ['newMonitorActivity'];
|
||||
|
||||
public function hydrateActivity()
|
||||
{
|
||||
$this->activity = Activity::query()
|
||||
->find($this->activityId);
|
||||
}
|
||||
|
||||
public function newMonitorActivity($activityId)
|
||||
{
|
||||
$this->activityId = $activityId;
|
||||
@ -30,6 +24,12 @@ public function newMonitorActivity($activityId)
|
||||
$this->isPollingActive = true;
|
||||
}
|
||||
|
||||
public function hydrateActivity()
|
||||
{
|
||||
$this->activity = Activity::query()
|
||||
->find($this->activityId);
|
||||
}
|
||||
|
||||
public function polling()
|
||||
{
|
||||
$this->hydrateActivity();
|
||||
@ -45,6 +45,7 @@ public function polling()
|
||||
$this->emit('activityFinished');
|
||||
}
|
||||
}
|
||||
|
||||
protected function setStatus($status)
|
||||
{
|
||||
$this->activity->properties = $this->activity->properties->merge([
|
||||
|
@ -19,11 +19,13 @@ class CheckLicense extends Component
|
||||
'instance_id' => 'Instance Id (Do not change this)',
|
||||
'settings.is_resale_license_active' => 'Is License Active',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->instance_id = config('app.id');
|
||||
$this->settings = InstanceSettings::get();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
|
@ -18,11 +18,13 @@ class Form extends Component
|
||||
'destination.network' => 'network',
|
||||
'destination.server.ip' => 'IP Address',
|
||||
];
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$this->destination->save();
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
|
@ -5,9 +5,9 @@
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker as ModelsStandaloneDocker;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class StandaloneDocker extends Component
|
||||
{
|
||||
@ -28,6 +28,7 @@ class StandaloneDocker extends Component
|
||||
'network' => 'network',
|
||||
'server_id' => 'server'
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (request()->query('server_id')) {
|
||||
@ -44,15 +45,13 @@ public function mount()
|
||||
}
|
||||
$this->name = Str::kebab("{$this->servers->first()->name}-{$this->network}");
|
||||
}
|
||||
public function generate_name() {
|
||||
|
||||
public function generate_name()
|
||||
{
|
||||
$this->server = Server::find($this->server_id);
|
||||
$this->name = Str::kebab("{$this->server->name}-{$this->network}");
|
||||
}
|
||||
private function createNetworkAndAttachToProxy()
|
||||
{
|
||||
instant_remote_process(['docker network create --attachable ' . $this->network], $this->server, throwError: false);
|
||||
instant_remote_process(["docker network connect $this->network coolify-proxy"], $this->server, throwError: false);
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
@ -77,4 +76,10 @@ public function submit()
|
||||
return general_error_handler(err: $e);
|
||||
}
|
||||
}
|
||||
|
||||
private function createNetworkAndAttachToProxy()
|
||||
{
|
||||
instant_remote_process(['docker network create --attachable ' . $this->network], $this->server, throwError: false);
|
||||
instant_remote_process(["docker network connect $this->network coolify-proxy"], $this->server, throwError: false);
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ class Show extends Component
|
||||
{
|
||||
public Server $server;
|
||||
public Collection|array $networks = [];
|
||||
|
||||
public function scan()
|
||||
{
|
||||
$alreadyAddedNetworks = $this->server->standaloneDockers;
|
||||
|
@ -4,30 +4,36 @@
|
||||
|
||||
use App\Models\S3Storage;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\WithFileUploads;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
class S3Test extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public $s3;
|
||||
public $file;
|
||||
public function mount() {
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->s3 = S3Storage::first();
|
||||
}
|
||||
public function save() {
|
||||
|
||||
public function save()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'file' => 'required|max:150', // 1MB Max
|
||||
]);
|
||||
set_s3_target($this->s3);
|
||||
$this->file->storeAs('files', $this->file->getClientOriginalName(),'custom-s3');
|
||||
$this->file->storeAs('files', $this->file->getClientOriginalName(), 'custom-s3');
|
||||
$this->emit('success', 'File uploaded successfully.');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function get_files()
|
||||
{
|
||||
set_s3_target($this->s3);
|
||||
|
@ -19,6 +19,7 @@ class DiscordSettings extends Component
|
||||
protected $validationAttributes = [
|
||||
'model.discord_webhook_url' => 'Discord Webhook',
|
||||
];
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
@ -29,6 +30,14 @@ public function instantSave()
|
||||
$this->validate();
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
$this->validate();
|
||||
$this->saveModel();
|
||||
}
|
||||
|
||||
public function saveModel()
|
||||
{
|
||||
ray($this->model);
|
||||
@ -38,12 +47,7 @@ public function saveModel()
|
||||
}
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
$this->validate();
|
||||
$this->saveModel();
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->model->notify(new Test);
|
||||
|
@ -37,6 +37,13 @@ class EmailSettings extends Component
|
||||
'model.smtp_username' => 'Username',
|
||||
'model.smtp_password' => 'Password',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->decrypt();
|
||||
$this->emails = auth()->user()->email;
|
||||
}
|
||||
|
||||
private function decrypt()
|
||||
{
|
||||
if (data_get($this->model, 'smtp_password')) {
|
||||
@ -46,11 +53,7 @@ private function decrypt()
|
||||
}
|
||||
}
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->decrypt();
|
||||
$this->emails = auth()->user()->email;
|
||||
}
|
||||
|
||||
public function copyFromInstanceSettings()
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
@ -78,6 +81,23 @@ public function copyFromInstanceSettings()
|
||||
$this->emit('error', 'Instance SMTP settings are not enabled.');
|
||||
}
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->model->notify(new Test($this->emails));
|
||||
$this->emit('success', 'Test Email sent successfully.');
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->submit();
|
||||
} catch (\Exception $e) {
|
||||
$this->model->smtp_enabled = false;
|
||||
$this->validate();
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
@ -92,6 +112,7 @@ public function submit()
|
||||
$this->model->smtp_recipients = str_replace(' ', '', $this->model->smtp_recipients);
|
||||
$this->saveModel();
|
||||
}
|
||||
|
||||
public function saveModel()
|
||||
{
|
||||
$this->model->save();
|
||||
@ -101,18 +122,4 @@ public function saveModel()
|
||||
}
|
||||
$this->emit('success', 'Settings saved.');
|
||||
}
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->model->notify(new Test($this->emails));
|
||||
$this->emit('success', 'Test Email sent successfully.');
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->submit();
|
||||
} catch (\Exception $e) {
|
||||
$this->model->smtp_enabled = false;
|
||||
$this->validate();
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ class Change extends Component
|
||||
'private_key.description' => 'description',
|
||||
'private_key.private_key' => 'private key'
|
||||
];
|
||||
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
@ -33,6 +34,7 @@ public function delete()
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function changePrivateKey()
|
||||
{
|
||||
try {
|
||||
|
@ -19,6 +19,7 @@ class Create extends Component
|
||||
'name' => 'name',
|
||||
'value' => 'private Key',
|
||||
];
|
||||
|
||||
public function createPrivateKey()
|
||||
{
|
||||
$this->validate();
|
||||
|
@ -17,12 +17,14 @@ class Form extends Component
|
||||
protected $validationAttributes = [
|
||||
'name' => 'name',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->userId = auth()->user()->id;
|
||||
$this->name = auth()->user()->name;
|
||||
$this->email = auth()->user()->email;
|
||||
}
|
||||
|
||||
public function submit()
|
||||
|
||||
{
|
||||
|
@ -17,6 +17,7 @@ class AddEmpty extends Component
|
||||
'name' => 'Project Name',
|
||||
'description' => 'Project Description',
|
||||
];
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
@ -17,6 +17,7 @@ class AddEnvironment extends Component
|
||||
protected $validationAttributes = [
|
||||
'name' => 'Environment Name',
|
||||
];
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
@ -10,10 +10,12 @@ class DeploymentLogs extends Component
|
||||
public ApplicationDeploymentQueue $application_deployment_queue;
|
||||
public $isKeepAliveOn = true;
|
||||
protected $listeners = ['refreshQueue'];
|
||||
|
||||
public function refreshQueue()
|
||||
{
|
||||
$this->application_deployment_queue->refresh();
|
||||
}
|
||||
|
||||
public function polling()
|
||||
{
|
||||
$this->emit('deploymentFinished');
|
||||
|
@ -8,17 +8,16 @@
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
|
||||
class DeploymentNavbar extends Component
|
||||
{
|
||||
protected $listeners = ['deploymentFinished'];
|
||||
|
||||
public ApplicationDeploymentQueue $application_deployment_queue;
|
||||
public Application $application;
|
||||
public Server $server;
|
||||
public bool $is_debug_enabled = false;
|
||||
protected $listeners = ['deploymentFinished'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@ -26,10 +25,12 @@ public function mount()
|
||||
$this->server = $this->application->destination->server;
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
}
|
||||
|
||||
public function deploymentFinished()
|
||||
{
|
||||
$this->application_deployment_queue->refresh();
|
||||
}
|
||||
|
||||
public function show_debug()
|
||||
{
|
||||
$this->application->settings->is_debug_enabled = !$this->application->settings->is_debug_enabled;
|
||||
@ -37,6 +38,7 @@ public function show_debug()
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->emit('refreshQueue');
|
||||
}
|
||||
|
||||
public function cancel()
|
||||
{
|
||||
try {
|
||||
|
@ -20,6 +20,7 @@ public function mount()
|
||||
$this->current_url = url()->current();
|
||||
$this->show_more();
|
||||
}
|
||||
|
||||
private function show_more()
|
||||
{
|
||||
if (count($this->deployments) !== 0) {
|
||||
@ -30,10 +31,12 @@ private function show_more()
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function reload_deployments()
|
||||
{
|
||||
$this->load_deployments();
|
||||
}
|
||||
|
||||
public function load_deployments(int|null $take = null)
|
||||
{
|
||||
if ($take) {
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\InstanceSettings;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
|
||||
class General extends Component
|
||||
@ -65,6 +65,7 @@ class General extends Component
|
||||
'application.ports_exposes' => 'Ports exposes',
|
||||
'application.ports_mappings' => 'Ports mappings',
|
||||
];
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
// @TODO: find another way - if possible
|
||||
@ -86,6 +87,7 @@ public function instantSave()
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
$this->checkWildCardDomain();
|
||||
}
|
||||
|
||||
protected function checkWildCardDomain()
|
||||
{
|
||||
$coolify_instance_settings = InstanceSettings::get();
|
||||
@ -93,6 +95,7 @@ protected function checkWildCardDomain()
|
||||
$this->global_wildcard_domain = data_get($coolify_instance_settings, 'wildcard_domain');
|
||||
$this->wildcard_domain = $this->server_wildcard_domain ?? $this->global_wildcard_domain ?? null;
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->is_static = $this->application->settings->is_static;
|
||||
@ -104,6 +107,7 @@ public function mount()
|
||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||
$this->checkWildCardDomain();
|
||||
}
|
||||
|
||||
public function generateGlobalRandomDomain()
|
||||
{
|
||||
// Set wildcard domain based on Global wildcard domain
|
||||
@ -115,6 +119,7 @@ public function generateGlobalRandomDomain()
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
}
|
||||
|
||||
public function generateServerRandomDomain()
|
||||
{
|
||||
// Set wildcard domain based on Server wildcard domain
|
||||
@ -126,6 +131,7 @@ public function generateServerRandomDomain()
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
@ -28,6 +28,12 @@ public function check_status()
|
||||
));
|
||||
$this->application->refresh();
|
||||
}
|
||||
|
||||
public function force_deploy_without_cache()
|
||||
{
|
||||
$this->deploy(force_rebuild: true);
|
||||
}
|
||||
|
||||
public function deploy(bool $force_rebuild = false)
|
||||
{
|
||||
$this->setDeploymentUuid();
|
||||
@ -43,10 +49,13 @@ public function deploy(bool $force_rebuild = false)
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
]);
|
||||
}
|
||||
public function force_deploy_without_cache()
|
||||
|
||||
protected function setDeploymentUuid()
|
||||
{
|
||||
$this->deploy(force_rebuild: true);
|
||||
$this->deploymentUuid = new Cuid2(7);
|
||||
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
|
||||
}
|
||||
|
||||
public function stop()
|
||||
{
|
||||
remote_process(
|
||||
@ -57,9 +66,4 @@ public function stop()
|
||||
$this->application->save();
|
||||
$this->application->environment->project->team->notify(new StatusChanged($this->application));
|
||||
}
|
||||
protected function setDeploymentUuid()
|
||||
{
|
||||
$this->deploymentUuid = new Cuid2(7);
|
||||
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ class Form extends Component
|
||||
protected $validationAttributes = [
|
||||
'application.preview_url_template' => 'preview url template',
|
||||
];
|
||||
|
||||
public function resetToDefault()
|
||||
{
|
||||
$this->application->preview_url_template = '{{pr_id}}.{{domain}}';
|
||||
@ -24,6 +25,7 @@ public function resetToDefault()
|
||||
$this->application->save();
|
||||
$this->generate_real_url();
|
||||
}
|
||||
|
||||
public function generate_real_url()
|
||||
{
|
||||
if (data_get($this->application, 'fqdn')) {
|
||||
@ -32,10 +34,12 @@ public function generate_real_url()
|
||||
$this->preview_url_template = Str::of($this->application->preview_url_template)->replace('{{domain}}', $host);
|
||||
}
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->generate_real_url();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
|
@ -22,6 +22,7 @@ public function mount()
|
||||
$this->pull_requests = collect();
|
||||
$this->parameters = getRouteParameters();
|
||||
}
|
||||
|
||||
public function loadStatus($pull_request_id)
|
||||
{
|
||||
dispatch(new ContainerStatusJob(
|
||||
@ -30,11 +31,7 @@ public function loadStatus($pull_request_id)
|
||||
pull_request_id: $pull_request_id
|
||||
));
|
||||
}
|
||||
protected function setDeploymentUuid()
|
||||
{
|
||||
$this->deployment_uuid = new Cuid2(7);
|
||||
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
|
||||
}
|
||||
|
||||
public function load_prs()
|
||||
{
|
||||
try {
|
||||
@ -46,6 +43,7 @@ public function load_prs()
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function deploy(int $pull_request_id, string|null $pull_request_html_url = null)
|
||||
{
|
||||
try {
|
||||
@ -74,6 +72,13 @@ public function deploy(int $pull_request_id, string|null $pull_request_html_url
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
protected function setDeploymentUuid()
|
||||
{
|
||||
$this->deployment_uuid = new Cuid2(7);
|
||||
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
|
||||
}
|
||||
|
||||
public function stop(int $pull_request_id)
|
||||
{
|
||||
try {
|
||||
@ -87,6 +92,7 @@ public function stop(int $pull_request_id)
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function previewRefresh()
|
||||
{
|
||||
$this->application->previews->each(function ($preview) {
|
||||
|
@ -3,8 +3,8 @@
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Rollback extends Component
|
||||
@ -18,6 +18,7 @@ public function mount()
|
||||
{
|
||||
$this->parameters = getRouteParameters();
|
||||
}
|
||||
|
||||
public function rollbackImage($commit)
|
||||
{
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
@ -36,6 +37,7 @@ public function rollbackImage($commit)
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function loadImages()
|
||||
{
|
||||
try {
|
||||
|
@ -21,16 +21,19 @@ class Source extends Component
|
||||
'application.git_branch' => 'branch',
|
||||
'application.git_commit_sha' => 'commit sha',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->get_private_keys();
|
||||
}
|
||||
|
||||
private function get_private_keys()
|
||||
{
|
||||
$this->private_keys = PrivateKey::whereTeamId(session('currentTeam')->id)->get()->reject(function ($key) {
|
||||
return $key->id == $this->application->private_key_id;
|
||||
});
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->get_private_keys();
|
||||
}
|
||||
|
||||
public function setPrivateKey(int $private_key_id)
|
||||
{
|
||||
$this->application->private_key_id = $private_key_id;
|
||||
@ -38,6 +41,7 @@ public function setPrivateKey(int $private_key_id)
|
||||
$this->application->refresh();
|
||||
$this->get_private_keys();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Database;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Actions\Database\StartPostgresql;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use Livewire\Component;
|
||||
|
||||
class Heading extends Component
|
||||
{
|
||||
@ -13,13 +13,16 @@ class Heading extends Component
|
||||
public array $parameters;
|
||||
|
||||
protected $listeners = ['activityFinished'];
|
||||
public function activityFinished() {
|
||||
|
||||
public function activityFinished()
|
||||
{
|
||||
$this->database->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$this->emit('refresh');
|
||||
$this->check_status();
|
||||
}
|
||||
|
||||
public function check_status()
|
||||
{
|
||||
dispatch_sync(new ContainerStatusJob(
|
||||
@ -28,11 +31,14 @@ public function check_status()
|
||||
));
|
||||
$this->database->refresh();
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = getRouteParameters();
|
||||
}
|
||||
public function stop() {
|
||||
|
||||
public function stop()
|
||||
{
|
||||
remote_process(
|
||||
["docker rm -f {$this->database->uuid}"],
|
||||
$this->database->destination->server
|
||||
@ -41,7 +47,9 @@ public function stop() {
|
||||
$this->database->save();
|
||||
$this->database->environment->project->team->notify(new StatusChanged($this->database));
|
||||
}
|
||||
public function start() {
|
||||
|
||||
public function start()
|
||||
{
|
||||
if ($this->database->type() === 'standalone-postgresql') {
|
||||
$activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
|
@ -31,10 +31,14 @@ class General extends Component
|
||||
'database.init_scripts' => 'Init Scripts',
|
||||
'database.image' => 'Image',
|
||||
];
|
||||
public function refresh() {
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->database->refresh();
|
||||
}
|
||||
public function submit() {
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->database->save();
|
||||
|
@ -14,6 +14,7 @@ public function mount()
|
||||
{
|
||||
$this->parameters = getRouteParameters();
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$this->validate([
|
||||
|
@ -14,6 +14,7 @@ public function mount()
|
||||
{
|
||||
$this->parameters = getRouteParameters();
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$this->validate([
|
||||
|
@ -5,12 +5,9 @@
|
||||
use App\Models\Application;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Livewire\Component;
|
||||
|
||||
class GithubPrivateRepository extends Component
|
||||
@ -30,18 +27,14 @@ class GithubPrivateRepository extends Component
|
||||
public string $selected_branch_name = 'main';
|
||||
|
||||
public string $token;
|
||||
|
||||
protected int $page = 1;
|
||||
|
||||
public $repositories;
|
||||
public int $total_repositories_count = 0;
|
||||
|
||||
public $branches;
|
||||
public int $total_branches_count = 0;
|
||||
|
||||
public int $port = 3000;
|
||||
public bool $is_static = false;
|
||||
public string|null $publish_directory = null;
|
||||
protected int $page = 1;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@ -50,32 +43,7 @@ public function mount()
|
||||
$this->repositories = $this->branches = collect();
|
||||
$this->github_apps = GithubApp::private();
|
||||
}
|
||||
protected function loadRepositoryByPage()
|
||||
{
|
||||
$response = Http::withToken($this->token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100&page={$this->page}");
|
||||
$json = $response->json();
|
||||
if ($response->status() !== 200) {
|
||||
return $this->emit('error', $json['message']);
|
||||
}
|
||||
|
||||
if ($json['total_count'] === 0) {
|
||||
return;
|
||||
}
|
||||
$this->total_repositories_count = $json['total_count'];
|
||||
$this->repositories = $this->repositories->concat(collect($json['repositories']));
|
||||
}
|
||||
protected function loadBranchByPage()
|
||||
{
|
||||
ray('Loading page ' . $this->page);
|
||||
$response = Http::withToken($this->token)->get("{$this->github_app->api_url}/repos/{$this->selected_repository_owner}/{$this->selected_repository_repo}/branches?per_page=100&page={$this->page}");
|
||||
$json = $response->json();
|
||||
if ($response->status() !== 200) {
|
||||
return $this->emit('error', $json['message']);
|
||||
}
|
||||
|
||||
$this->total_branches_count = count($json);
|
||||
$this->branches = $this->branches->concat(collect($json));
|
||||
}
|
||||
public function loadRepositories($github_app_id)
|
||||
{
|
||||
$this->repositories = collect();
|
||||
@ -93,6 +61,22 @@ public function loadRepositories($github_app_id)
|
||||
$this->selected_repository_id = $this->repositories[0]['id'];
|
||||
$this->current_step = 'repository';
|
||||
}
|
||||
|
||||
protected function loadRepositoryByPage()
|
||||
{
|
||||
$response = Http::withToken($this->token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100&page={$this->page}");
|
||||
$json = $response->json();
|
||||
if ($response->status() !== 200) {
|
||||
return $this->emit('error', $json['message']);
|
||||
}
|
||||
|
||||
if ($json['total_count'] === 0) {
|
||||
return;
|
||||
}
|
||||
$this->total_repositories_count = $json['total_count'];
|
||||
$this->repositories = $this->repositories->concat(collect($json['repositories']));
|
||||
}
|
||||
|
||||
public function loadBranches()
|
||||
{
|
||||
$this->selected_repository_owner = $this->repositories->where('id', $this->selected_repository_id)->first()['owner']['login'];
|
||||
@ -107,6 +91,20 @@ public function loadBranches()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function loadBranchByPage()
|
||||
{
|
||||
ray('Loading page ' . $this->page);
|
||||
$response = Http::withToken($this->token)->get("{$this->github_app->api_url}/repos/{$this->selected_repository_owner}/{$this->selected_repository_repo}/branches?per_page=100&page={$this->page}");
|
||||
$json = $response->json();
|
||||
if ($response->status() !== 200) {
|
||||
return $this->emit('error', $json['message']);
|
||||
}
|
||||
|
||||
$this->total_branches_count = count($json);
|
||||
$this->branches = $this->branches->concat(collect($json));
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
@ -136,7 +134,7 @@ public function submit()
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination_class,
|
||||
'source_id' => $this->github_app->id,
|
||||
'source_type' => $this->github_app->getMorphClass()
|
||||
'source_type' => $this->github_app->getMorphClass()
|
||||
]);
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
@ -150,6 +148,7 @@ public function submit()
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
if ($this->is_static) {
|
||||
|
@ -27,14 +27,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
public null|string $publish_directory = null;
|
||||
|
||||
public string $repository_url;
|
||||
private object $repository_url_parsed;
|
||||
public string $branch;
|
||||
|
||||
private GithubApp|GitlabApp $git_source;
|
||||
private string $git_host;
|
||||
private string $git_repository;
|
||||
private string $git_branch;
|
||||
|
||||
protected $rules = [
|
||||
'repository_url' => 'required|url',
|
||||
'branch' => 'required|string',
|
||||
@ -49,6 +42,12 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
'is_static' => 'Is static',
|
||||
'publish_directory' => 'Publish directory',
|
||||
];
|
||||
private object $repository_url_parsed;
|
||||
private GithubApp|GitlabApp $git_source;
|
||||
private string $git_host;
|
||||
private string $git_repository;
|
||||
private string $git_branch;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (isDev()) {
|
||||
@ -58,6 +57,7 @@ public function mount()
|
||||
$this->query = request()->query();
|
||||
$this->private_keys = PrivateKey::where('team_id', session('currentTeam')->id)->where('id', '!=', 0)->get();
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
if ($this->is_static) {
|
||||
@ -68,30 +68,13 @@ public function instantSave()
|
||||
$this->publish_directory = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function setPrivateKey($private_key_id)
|
||||
{
|
||||
$this->private_key_id = $private_key_id;
|
||||
$this->current_step = 'repository';
|
||||
}
|
||||
private function get_git_source()
|
||||
{
|
||||
$this->repository_url_parsed = Url::fromString($this->repository_url);
|
||||
$this->git_host = $this->repository_url_parsed->getHost();
|
||||
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
|
||||
if ($this->branch) {
|
||||
$this->git_branch = $this->branch;
|
||||
} else {
|
||||
$this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main';
|
||||
}
|
||||
|
||||
if ($this->git_host == 'github.com') {
|
||||
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
|
||||
} elseif ($this->git_host == 'gitlab.com') {
|
||||
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
|
||||
} elseif ($this->git_host == 'bitbucket.org') {
|
||||
// Not supported yet
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
@ -123,7 +106,7 @@ public function submit()
|
||||
'destination_type' => $destination_class,
|
||||
'private_key_id' => $this->private_key_id,
|
||||
'source_id' => $this->git_source->id,
|
||||
'source_type' => $this->git_source->getMorphClass()
|
||||
'source_type' => $this->git_source->getMorphClass()
|
||||
];
|
||||
$application = Application::create($application_init);
|
||||
$application->settings->is_static = $this->is_static;
|
||||
@ -138,4 +121,24 @@ public function submit()
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
private function get_git_source()
|
||||
{
|
||||
$this->repository_url_parsed = Url::fromString($this->repository_url);
|
||||
$this->git_host = $this->repository_url_parsed->getHost();
|
||||
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
|
||||
if ($this->branch) {
|
||||
$this->git_branch = $this->branch;
|
||||
} else {
|
||||
$this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main';
|
||||
}
|
||||
|
||||
if ($this->git_host == 'github.com') {
|
||||
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
|
||||
} elseif ($this->git_host == 'gitlab.com') {
|
||||
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
|
||||
} elseif ($this->git_host == 'bitbucket.org') {
|
||||
// Not supported yet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,10 @@
|
||||
class PublicGitRepository extends Component
|
||||
{
|
||||
public string $repository_url;
|
||||
private object $repository_url_parsed;
|
||||
|
||||
public int $port = 3000;
|
||||
public string $type;
|
||||
public $parameters;
|
||||
public $query;
|
||||
|
||||
public bool $branch_found = false;
|
||||
public string $selected_branch = 'main';
|
||||
public bool $is_static = false;
|
||||
@ -29,11 +26,6 @@ class PublicGitRepository extends Component
|
||||
public string $git_branch = 'main';
|
||||
public int $rate_limit_remaining = 0;
|
||||
public $rate_limit_reset = 0;
|
||||
|
||||
private GithubApp|GitlabApp $git_source;
|
||||
private string $git_host;
|
||||
private string $git_repository;
|
||||
|
||||
protected $rules = [
|
||||
'repository_url' => 'required|url',
|
||||
'port' => 'required|numeric',
|
||||
@ -46,6 +38,11 @@ class PublicGitRepository extends Component
|
||||
'is_static' => 'static',
|
||||
'publish_directory' => 'publish directory',
|
||||
];
|
||||
private object $repository_url_parsed;
|
||||
private GithubApp|GitlabApp $git_source;
|
||||
private string $git_host;
|
||||
private string $git_repository;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (isDev()) {
|
||||
@ -67,12 +64,7 @@ public function instantSave()
|
||||
}
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
}
|
||||
private function get_branch()
|
||||
{
|
||||
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = git_api(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
|
||||
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
|
||||
$this->branch_found = true;
|
||||
}
|
||||
|
||||
public function load_branch()
|
||||
{
|
||||
$this->branch_found = false;
|
||||
@ -96,6 +88,7 @@ public function load_branch()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function get_git_source()
|
||||
{
|
||||
$this->repository_url_parsed = Url::fromString($this->repository_url);
|
||||
@ -111,6 +104,14 @@ private function get_git_source()
|
||||
// Not supported yet
|
||||
}
|
||||
}
|
||||
|
||||
private function get_branch()
|
||||
{
|
||||
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = git_api(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
|
||||
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
|
||||
$this->branch_found = true;
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
@ -19,17 +19,20 @@ public function mount()
|
||||
{
|
||||
$this->parameters = getRouteParameters();
|
||||
}
|
||||
|
||||
public function set_type(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->current_step = 'servers';
|
||||
}
|
||||
|
||||
public function set_server(Server $server)
|
||||
{
|
||||
$this->server_id = $server->id;
|
||||
$this->destinations = $server->destinations();
|
||||
$this->current_step = 'destinations';
|
||||
}
|
||||
|
||||
public function set_destination(string $destination_uuid)
|
||||
{
|
||||
$this->destination_uuid = $destination_uuid;
|
||||
@ -40,6 +43,7 @@ public function set_destination(string $destination_uuid)
|
||||
'destination' => $this->destination_uuid,
|
||||
]);
|
||||
}
|
||||
|
||||
public function load_servers()
|
||||
{
|
||||
$this->servers = Server::ownedByCurrentTeam()->get();
|
||||
|
@ -16,6 +16,7 @@ public function mount()
|
||||
$this->modalId = new Cuid2(7);
|
||||
$this->parameters = getRouteParameters();
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
|
||||
|
@ -23,10 +23,12 @@ class Add extends Component
|
||||
'value' => 'value',
|
||||
'is_build_time' => 'build',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = getRouteParameters();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
ray('submitting');
|
||||
@ -39,6 +41,7 @@ public function submit()
|
||||
]);
|
||||
$this->clear();
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$this->key = '';
|
||||
|
@ -11,14 +11,17 @@ class All extends Component
|
||||
public $resource;
|
||||
public string|null $modalId = null;
|
||||
protected $listeners = ['refreshEnvs', 'submit'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->modalId = new Cuid2(7);
|
||||
}
|
||||
|
||||
public function refreshEnvs()
|
||||
{
|
||||
$this->resource->refresh();
|
||||
}
|
||||
|
||||
public function submit($data)
|
||||
{
|
||||
try {
|
||||
@ -27,16 +30,16 @@ public function submit($data)
|
||||
$this->emit('error', 'Environment variable already exists.');
|
||||
return;
|
||||
}
|
||||
$environment = new EnvironmentVariable();
|
||||
$environment = new EnvironmentVariable();
|
||||
$environment->key = $data['key'];
|
||||
$environment->value = $data['value'];
|
||||
$environment->is_build_time = $data['is_build_time'];
|
||||
$environment->is_preview = $data['is_preview'];
|
||||
|
||||
if($this->resource->type() === 'application') {
|
||||
if ($this->resource->type() === 'application') {
|
||||
$environment->application_id = $this->resource->id;
|
||||
}
|
||||
if($this->resource->type() === 'standalone-postgresql') {
|
||||
if ($this->resource->type() === 'standalone-postgresql') {
|
||||
$environment->standalone_postgresql_id = $this->resource->id;
|
||||
}
|
||||
$environment->save();
|
||||
|
@ -21,17 +21,20 @@ class Show extends Component
|
||||
'value' => 'value',
|
||||
'is_build_time' => 'build',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->modalId = new Cuid2(7);
|
||||
$this->parameters = getRouteParameters();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$this->env->save();
|
||||
$this->emit('success', 'Environment variable updated successfully.');
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$this->env->delete();
|
||||
|
@ -25,6 +25,7 @@ class ResourceLimits extends Component
|
||||
'resource.limits_cpuset' => 'cpuset',
|
||||
'resource.limits_cpu_shares' => 'cpu shares',
|
||||
];
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
@ -22,10 +22,12 @@ class Add extends Component
|
||||
'mount_path' => 'mount',
|
||||
'host_path' => 'host',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = getRouteParameters();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
@ -35,6 +37,7 @@ public function submit()
|
||||
'host_path' => $this->host_path,
|
||||
]);
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$this->name = '';
|
||||
|
@ -9,10 +9,12 @@ class All extends Component
|
||||
{
|
||||
public $resource;
|
||||
protected $listeners = ['refreshStorages', 'submit'];
|
||||
|
||||
public function refreshStorages()
|
||||
{
|
||||
$this->resource->refresh();
|
||||
}
|
||||
|
||||
public function submit($data)
|
||||
{
|
||||
try {
|
||||
|
@ -19,16 +19,19 @@ class Show extends Component
|
||||
'mount_path' => 'mount',
|
||||
'host_path' => 'host',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->modalId = new Cuid2(7);
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$this->storage->save();
|
||||
$this->emit('success', 'Storage updated successfully');
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$this->storage->delete();
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Enums\ActivityTypes;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
@ -20,6 +19,7 @@ class RunCommand extends Component
|
||||
'server' => 'server',
|
||||
'command' => 'command',
|
||||
];
|
||||
|
||||
public function mount($servers)
|
||||
{
|
||||
$this->servers = $servers;
|
||||
|
@ -5,7 +5,6 @@
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
@ -34,16 +33,19 @@ class Form extends Component
|
||||
'server.settings.is_reachable' => 'is reachable',
|
||||
'server.settings.is_part_of_swarm' => 'is part of swarm'
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||
}
|
||||
|
||||
public function installDocker()
|
||||
{
|
||||
$activity = resolve(InstallDocker::class)($this->server, session('currentTeam'));
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
|
||||
public function validateServer()
|
||||
{
|
||||
try {
|
||||
@ -59,6 +61,7 @@ public function validateServer()
|
||||
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
if (!$this->server->isEmpty()) {
|
||||
@ -68,6 +71,7 @@ public function delete()
|
||||
$this->server->delete();
|
||||
redirect()->route('server.all');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Livewire\Server\New;
|
||||
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
@ -35,19 +34,23 @@ class ByIp extends Component
|
||||
'user' => 'user',
|
||||
'port' => 'port',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->name = generate_random_name();
|
||||
$this->private_key_id = $this->private_keys->first()->id;
|
||||
}
|
||||
|
||||
public function setPrivateKey(string $private_key_id)
|
||||
{
|
||||
$this->private_key_id = $private_key_id;
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Http\Livewire\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Component;
|
||||
use Masmerise\Toaster\Toaster;
|
||||
|
||||
@ -13,6 +12,16 @@ class PrivateKey extends Component
|
||||
public $privateKeys;
|
||||
public $parameters;
|
||||
|
||||
public function setPrivateKey($private_key_id)
|
||||
{
|
||||
$this->server->update([
|
||||
'private_key_id' => $private_key_id
|
||||
]);
|
||||
refreshPrivateKey($this->server->privateKey);
|
||||
$this->server->refresh();
|
||||
$this->checkConnection();
|
||||
}
|
||||
|
||||
public function checkConnection()
|
||||
{
|
||||
try {
|
||||
@ -27,15 +36,7 @@ public function checkConnection()
|
||||
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
|
||||
}
|
||||
}
|
||||
public function setPrivateKey($private_key_id)
|
||||
{
|
||||
$this->server->update([
|
||||
'private_key_id' => $private_key_id
|
||||
]);
|
||||
refreshPrivateKey($this->server->privateKey);
|
||||
$this->server->refresh();
|
||||
$this->checkConnection();
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = getRouteParameters();
|
||||
|
@ -5,7 +5,6 @@
|
||||
use App\Actions\Proxy\CheckConfigurationSync;
|
||||
use App\Actions\Proxy\SaveConfigurationSync;
|
||||
use App\Enums\ProxyTypes;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
@ -17,21 +16,25 @@ class Proxy extends Component
|
||||
public $proxy_settings = null;
|
||||
public string|null $redirect_url = null;
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration'=>'submit'];
|
||||
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->redirect_url = $this->server->proxy->redirect_url;
|
||||
}
|
||||
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->server->refresh();
|
||||
}
|
||||
|
||||
public function change_proxy()
|
||||
{
|
||||
$this->server->proxy = null;
|
||||
$this->server->save();
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
|
||||
public function select_proxy(string $proxy_type)
|
||||
{
|
||||
$this->server->proxy->type = $proxy_type;
|
||||
@ -39,6 +42,7 @@ public function select_proxy(string $proxy_type)
|
||||
$this->server->save();
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
@ -53,6 +57,7 @@ public function submit()
|
||||
return general_error_handler(err: $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function reset_proxy_configuration()
|
||||
{
|
||||
try {
|
||||
@ -61,6 +66,7 @@ public function reset_proxy_configuration()
|
||||
return general_error_handler(err: $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function load_proxy_configuration()
|
||||
{
|
||||
try {
|
||||
|
@ -5,12 +5,12 @@
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
use Str;
|
||||
|
||||
class Deploy extends Component
|
||||
{
|
||||
public Server $server;
|
||||
public $proxy_settings = null;
|
||||
|
||||
public function start_proxy()
|
||||
{
|
||||
if (
|
||||
@ -22,6 +22,7 @@ public function start_proxy()
|
||||
$activity = resolve(StartProxy::class)($this->server);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
|
||||
public function stop()
|
||||
{
|
||||
instant_remote_process([
|
||||
|
@ -9,6 +9,7 @@
|
||||
class Status extends Component
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
public function get_status()
|
||||
{
|
||||
dispatch_sync(new ProxyContainerStatusJob(
|
||||
|
@ -31,6 +31,7 @@ class Configuration extends Component
|
||||
'settings.public_port_min' => 'Public port min',
|
||||
'settings.public_port_max' => 'Public port max',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->do_not_track = $this->settings->do_not_track;
|
||||
@ -38,6 +39,7 @@ public function mount()
|
||||
$this->is_registration_enabled = $this->settings->is_registration_enabled;
|
||||
$this->next_channel = $this->settings->next_channel;
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
$this->settings->do_not_track = $this->do_not_track;
|
||||
@ -47,6 +49,21 @@ public function instantSave()
|
||||
$this->settings->save();
|
||||
$this->emit('success', 'Settings updated!');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
if ($this->settings->public_port_min > $this->settings->public_port_max) {
|
||||
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
|
||||
return;
|
||||
}
|
||||
$this->validate();
|
||||
$this->settings->save();
|
||||
$this->server = Server::findOrFail(0);
|
||||
$this->setup_instance_fqdn();
|
||||
$this->emit('success', 'Instance settings updated successfully!');
|
||||
}
|
||||
|
||||
private function setup_instance_fqdn()
|
||||
{
|
||||
$file = "$this->dynamic_config_path/coolify.yaml";
|
||||
@ -60,35 +77,35 @@ private function setup_instance_fqdn()
|
||||
$schema = $url->getScheme();
|
||||
$traefik_dynamic_conf = [
|
||||
'http' =>
|
||||
[
|
||||
'routers' =>
|
||||
[
|
||||
'coolify-http' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'coolify' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
'routers' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
'coolify-http' =>
|
||||
[
|
||||
'url' => 'http://coolify:80',
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'coolify' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => 'http://coolify:80',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if ($schema === 'https') {
|
||||
@ -110,6 +127,7 @@ private function setup_instance_fqdn()
|
||||
dispatch(new ProxyStartJob($this->server));
|
||||
}
|
||||
}
|
||||
|
||||
private function save_configuration_to_disk(array $traefik_dynamic_conf, string $file)
|
||||
{
|
||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||
@ -128,17 +146,4 @@ private function save_configuration_to_disk(array $traefik_dynamic_conf, string
|
||||
ray($yaml);
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
if ($this->settings->public_port_min > $this->settings->public_port_max) {
|
||||
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
|
||||
return;
|
||||
}
|
||||
$this->validate();
|
||||
$this->settings->save();
|
||||
$this->server = Server::findOrFail(0);
|
||||
$this->setup_instance_fqdn();
|
||||
$this->emit('success', 'Instance settings updated successfully!');
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Notifications\TransactionalEmails\Test;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Livewire\Component;
|
||||
|
||||
class Email extends Component
|
||||
@ -32,11 +31,23 @@ class Email extends Component
|
||||
'settings.smtp_username' => 'Username',
|
||||
'settings.smtp_password' => 'Password',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->decrypt();
|
||||
$this->emails = auth()->user()->email;
|
||||
}
|
||||
|
||||
private function decrypt()
|
||||
{
|
||||
if (data_get($this->settings, 'smtp_password')) {
|
||||
try {
|
||||
$this->settings->smtp_password = decrypt($this->settings->smtp_password);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
@ -47,20 +58,7 @@ public function instantSave()
|
||||
$this->validate();
|
||||
}
|
||||
}
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->settings->notify(new Test($this->emails));
|
||||
$this->emit('success', 'Test email sent.');
|
||||
}
|
||||
private function decrypt()
|
||||
{
|
||||
if (data_get($this->settings, 'smtp_password')) {
|
||||
try {
|
||||
$this->settings->smtp_password = decrypt($this->settings->smtp_password);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
@ -75,4 +73,10 @@ public function submit()
|
||||
$this->emit('success', 'Transaction email settings updated successfully.');
|
||||
$this->decrypt();
|
||||
}
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->settings->notify(new Test($this->emails));
|
||||
$this->emit('success', 'Test email sent.');
|
||||
}
|
||||
}
|
@ -34,12 +34,14 @@ class Change extends Component
|
||||
'github_app.webhook_secret' => 'nullable',
|
||||
'github_app.is_system_wide' => 'required|bool',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->webhook_endpoint = $this->ipv4;
|
||||
$this->parameters = getRouteParameters();
|
||||
$this->is_system_wide = $this->github_app->is_system_wide;
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
@ -49,6 +51,7 @@ public function submit()
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ public function mount()
|
||||
{
|
||||
$this->name = generate_random_name();
|
||||
}
|
||||
|
||||
public function createGitHubApp()
|
||||
{
|
||||
try {
|
||||
|
@ -8,10 +8,12 @@
|
||||
class SwitchTeam extends Component
|
||||
{
|
||||
public string $selectedTeamId = 'default';
|
||||
|
||||
public function updatedSelectedTeamId()
|
||||
{
|
||||
$this->switch_to($this->selectedTeamId);
|
||||
}
|
||||
|
||||
public function switch_to($team_id)
|
||||
{
|
||||
if (!auth()->user()->teams->contains($team_id)) {
|
||||
|
@ -18,6 +18,7 @@ class Create extends Component
|
||||
'name' => 'name',
|
||||
'description' => 'description',
|
||||
];
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
use App\Models\Team;
|
||||
use Livewire\Component;
|
||||
use Masmerise\Toaster\Toaster;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
@ -17,10 +16,12 @@ class Form extends Component
|
||||
'team.name' => 'name',
|
||||
'team.description' => 'description',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->team = session('currentTeam');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
|
@ -9,13 +9,15 @@ class Invitations extends Component
|
||||
{
|
||||
public $invitations;
|
||||
protected $listeners = ['refreshInvitations'];
|
||||
public function refreshInvitations()
|
||||
{
|
||||
$this->invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get();
|
||||
}
|
||||
|
||||
public function deleteInvitation(int $invitation_id)
|
||||
{
|
||||
TeamInvitation::find($invitation_id)->delete();
|
||||
$this->refreshInvitations();
|
||||
}
|
||||
|
||||
public function refreshInvitations()
|
||||
{
|
||||
$this->invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get();
|
||||
}
|
||||
}
|
||||
|
@ -12,14 +12,17 @@ class InviteLink extends Component
|
||||
{
|
||||
public string $email;
|
||||
public string $role = 'member';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->email = isDev() ? 'test3@example.com' : '';
|
||||
}
|
||||
|
||||
public function viaEmail()
|
||||
{
|
||||
$this->generate_invite_link(isEmail: true);
|
||||
}
|
||||
|
||||
private function generate_invite_link(bool $isEmail = false)
|
||||
{
|
||||
try {
|
||||
@ -72,6 +75,7 @@ private function generate_invite_link(bool $isEmail = false)
|
||||
return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message);
|
||||
}
|
||||
}
|
||||
|
||||
public function viaLink()
|
||||
{
|
||||
$this->generate_invite_link();
|
||||
|
@ -8,16 +8,19 @@
|
||||
class Member extends Component
|
||||
{
|
||||
public User $member;
|
||||
|
||||
public function makeAdmin()
|
||||
{
|
||||
$this->member->teams()->updateExistingPivot(session('currentTeam')->id, ['role' => 'admin']);
|
||||
$this->emit('reloadWindow');
|
||||
}
|
||||
|
||||
public function makeReadonly()
|
||||
{
|
||||
$this->member->teams()->updateExistingPivot(session('currentTeam')->id, ['role' => 'member']);
|
||||
$this->emit('reloadWindow');
|
||||
}
|
||||
|
||||
public function remove()
|
||||
{
|
||||
$this->member->teams()->detach(session('currentTeam'));
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Http\Livewire\Team\Storage;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\S3Storage;
|
||||
use Livewire\Component;
|
||||
|
||||
class Create extends Component
|
||||
{
|
||||
@ -33,7 +33,9 @@ class Create extends Component
|
||||
'bucket' => 'Bucket',
|
||||
'endpoint' => 'Endpoint',
|
||||
];
|
||||
public function mount() {
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (isDev()) {
|
||||
$this->name = 'Local MinIO';
|
||||
$this->description = 'Local MinIO';
|
||||
@ -43,15 +45,9 @@ public function mount() {
|
||||
$this->endpoint = 'http://coolify-minio:9000';
|
||||
}
|
||||
}
|
||||
private function test_s3_connection() {
|
||||
try {
|
||||
$this->storage->testConnection();
|
||||
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
} catch(\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
}
|
||||
}
|
||||
public function submit() {
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->storage = new S3Storage();
|
||||
@ -71,9 +67,19 @@ public function submit() {
|
||||
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
$this->storage->save();
|
||||
return redirect()->route('team.storages.show', $this->storage->uuid);
|
||||
} catch(\Throwable $th) {
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function test_s3_connection()
|
||||
{
|
||||
try {
|
||||
$this->storage->testConnection();
|
||||
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Http\Livewire\Team\Storage;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\S3Storage;
|
||||
use Livewire\Component;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
@ -26,22 +26,27 @@ class Form extends Component
|
||||
'storage.bucket' => 'Bucket',
|
||||
'storage.endpoint' => 'Endpoint',
|
||||
];
|
||||
public function test_s3_connection() {
|
||||
|
||||
public function test_s3_connection()
|
||||
{
|
||||
try {
|
||||
$this->storage->testConnection();
|
||||
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
} catch(\Throwable $th) {
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
}
|
||||
}
|
||||
public function delete() {
|
||||
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$this->storage->delete();
|
||||
return redirect()->route('team.storages.all');
|
||||
} catch(\Throwable $th) {
|
||||
} catch (\Throwable $th) {
|
||||
return general_error_handler($th, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
use App\Actions\Server\UpdateCoolify;
|
||||
use App\Models\InstanceSettings;
|
||||
use Masmerise\Toaster\Toaster;
|
||||
use Livewire\Component;
|
||||
use Masmerise\Toaster\Toaster;
|
||||
|
||||
class Upgrade extends Component
|
||||
{
|
||||
@ -27,6 +27,7 @@ public function checkUpdate()
|
||||
$this->latestVersion = 'next';
|
||||
}
|
||||
}
|
||||
|
||||
public function upgrade()
|
||||
{
|
||||
try {
|
||||
|
@ -13,7 +13,7 @@ class RedirectIfAuthenticated
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ class TrustProxies extends Middleware
|
||||
* @var int
|
||||
*/
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
|
@ -12,8 +12,8 @@
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use App\Notifications\Application\DeploymentSuccess;
|
||||
use App\Notifications\Application\DeploymentFailed;
|
||||
use App\Notifications\Application\DeploymentSuccess;
|
||||
use App\Traits\ExecuteRemoteCommand;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@ -22,8 +22,8 @@
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Spatie\Url\Url;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Throwable;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
@ -61,6 +61,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
||||
|
||||
private $log_model;
|
||||
private Collection $saved_outputs;
|
||||
|
||||
public function __construct(int $application_deployment_queue_id)
|
||||
{
|
||||
ray()->clearScreen();
|
||||
@ -137,62 +138,7 @@ public function handle(): void
|
||||
// ray()->measure();
|
||||
}
|
||||
}
|
||||
public function failed(Throwable $exception): void
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
["echo 'Oops something is not okay, are you okay? 😢'"],
|
||||
["echo '{$exception->getMessage()}'"]
|
||||
);
|
||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
||||
}
|
||||
private function execute_in_builder(string $command)
|
||||
{
|
||||
return "docker exec {$this->deployment_uuid} bash -c '{$command}'";
|
||||
// return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'";
|
||||
}
|
||||
private function deploy()
|
||||
{
|
||||
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
|
||||
],
|
||||
);
|
||||
$this->prepare_builder_image();
|
||||
$this->clone_repository();
|
||||
|
||||
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
||||
if (strlen($tag) > 128) {
|
||||
$tag = $tag->substr(0, 128);
|
||||
};
|
||||
|
||||
$this->build_image_name = "{$this->application->git_repository}:{$tag}-build";
|
||||
$this->production_image_name = "{$this->application->uuid}:{$tag}";
|
||||
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||
|
||||
if (!$this->force_rebuild) {
|
||||
$this->execute_remote_command([
|
||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||
]);
|
||||
if (Str::of($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||
$this->execute_remote_command([
|
||||
"echo 'Docker Image found locally with the same Git Commit SHA {$this->application->uuid}:{$this->commit}. Build step skipped...'"
|
||||
]);
|
||||
$this->generate_compose_file();
|
||||
$this->stop_running_container();
|
||||
$this->start_by_compose_file();
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->cleanup_git();
|
||||
$this->generate_buildpack();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
$this->stop_running_container();
|
||||
$this->start_by_compose_file();
|
||||
}
|
||||
private function deploy_pull_request()
|
||||
{
|
||||
$this->build_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}-build";
|
||||
@ -214,117 +160,154 @@ private function deploy_pull_request()
|
||||
$this->start_by_compose_file();
|
||||
}
|
||||
|
||||
private function next(string $status)
|
||||
{
|
||||
// If the deployment is cancelled by the user, don't update the status
|
||||
if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => $status,
|
||||
]);
|
||||
}
|
||||
queue_next_deployment($this->application);
|
||||
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
||||
$this->application->environment->project->team->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
if ($status === ApplicationDeploymentStatus::FAILED->value) {
|
||||
$this->application->environment->project->team->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
}
|
||||
private function start_by_compose_file()
|
||||
private function prepare_builder_image()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting new application... '"],
|
||||
[$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
|
||||
["echo 'Done. 🎉'"],
|
||||
[
|
||||
"echo -n 'Pulling latest version of the builder image (ghcr.io/coollabsio/coolify-builder).'",
|
||||
],
|
||||
[
|
||||
"docker run --pull=always -d --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/coollabsio/coolify-builder",
|
||||
"hidden" => true,
|
||||
],
|
||||
[
|
||||
"command" => $this->execute_in_builder("mkdir -p {$this->workdir}")
|
||||
],
|
||||
);
|
||||
}
|
||||
private function stop_running_container()
|
||||
|
||||
private function execute_in_builder(string $command)
|
||||
{
|
||||
return "docker exec {$this->deployment_uuid} bash -c '{$command}'";
|
||||
// return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'";
|
||||
}
|
||||
|
||||
private function clone_repository()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Removing old running application.'"],
|
||||
[$this->execute_in_builder("docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true],
|
||||
[
|
||||
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}. '"
|
||||
],
|
||||
[
|
||||
$this->importing_git_repository()
|
||||
],
|
||||
[
|
||||
$this->execute_in_builder("cd {$this->workdir} && git rev-parse HEAD"),
|
||||
"hidden" => true,
|
||||
"save" => "git_commit_sha"
|
||||
],
|
||||
);
|
||||
$this->commit = $this->saved_outputs->get('git_commit_sha');
|
||||
}
|
||||
|
||||
private function importing_git_repository()
|
||||
{
|
||||
$commands = collect([]);
|
||||
$git_clone_command = "git clone -q -b {$this->application->git_branch}";
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$pr_branch_name = "pr-{$this->pull_request_id}-coolify";
|
||||
}
|
||||
|
||||
if ($this->application->deploymentType() === 'source') {
|
||||
$source_html_url = data_get($this->application, '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) {
|
||||
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}";
|
||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||
|
||||
$commands->push($this->execute_in_builder($git_clone_command));
|
||||
} else {
|
||||
$github_access_token = generate_github_installation_token($this->source);
|
||||
$commands->push($this->execute_in_builder("git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}"));
|
||||
}
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$commands->push($this->execute_in_builder("cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"));
|
||||
}
|
||||
return $commands->implode(' && ');
|
||||
}
|
||||
}
|
||||
if ($this->application->deploymentType() === 'deploy_key') {
|
||||
$private_key = base64_encode($this->application->private_key->private_key);
|
||||
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}";
|
||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||
$commands = collect([
|
||||
$this->execute_in_builder("mkdir -p /root/.ssh"),
|
||||
$this->execute_in_builder("echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
|
||||
$this->execute_in_builder("chmod 600 /root/.ssh/id_rsa"),
|
||||
$this->execute_in_builder($git_clone_command)
|
||||
]);
|
||||
return $commands->implode(' && ');
|
||||
}
|
||||
}
|
||||
|
||||
private function set_git_import_settings($git_clone_command)
|
||||
{
|
||||
if ($this->application->git_commit_sha !== 'HEAD') {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1";
|
||||
}
|
||||
if ($this->application->settings->is_git_submodules_enabled) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git submodule update --init --recursive";
|
||||
}
|
||||
if ($this->application->settings->is_git_lfs_enabled) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git lfs pull";
|
||||
}
|
||||
return $git_clone_command;
|
||||
}
|
||||
|
||||
private function cleanup_git()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[$this->execute_in_builder("rm -fr {$this->workdir}/.git")],
|
||||
);
|
||||
}
|
||||
private function build_image()
|
||||
|
||||
private function generate_buildpack()
|
||||
{
|
||||
$this->execute_remote_command([
|
||||
"echo -n 'Building docker image.'",
|
||||
]);
|
||||
|
||||
if ($this->application->settings->is_static) {
|
||||
$this->execute_remote_command([
|
||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
|
||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||
WORKDIR /usr/share/nginx/html/
|
||||
LABEL coolify.deploymentId={$this->deployment_uuid}
|
||||
COPY --from=$this->build_image_name /app/{$this->application->publish_directory} .
|
||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
|
||||
$nginx_config = base64_encode("server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files \$uri \$uri.html \$uri/index.html \$uri/ /index.html =404;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
$this->execute_in_builder("echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile-prod")
|
||||
],
|
||||
[
|
||||
$this->execute_in_builder("echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
|
||||
],
|
||||
[
|
||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command([
|
||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo -n 'Generating nixpacks configuration.'",
|
||||
],
|
||||
[$this->nixpacks_build_cmd()],
|
||||
[$this->execute_in_builder("cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
|
||||
[$this->execute_in_builder("rm -f {$this->workdir}/.nixpacks/Dockerfile")]
|
||||
);
|
||||
}
|
||||
private function add_build_env_variables_to_dockerfile()
|
||||
{
|
||||
$this->execute_remote_command([
|
||||
$this->execute_in_builder("cat {$this->workdir}/Dockerfile"), "hidden" => true, "save" => 'dockerfile'
|
||||
]);
|
||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}");
|
||||
}
|
||||
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
||||
$this->execute_remote_command([
|
||||
$this->execute_in_builder("echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/Dockerfile"),
|
||||
"hidden" => true
|
||||
]);
|
||||
}
|
||||
private function generate_build_env_variables()
|
||||
private function nixpacks_build_cmd()
|
||||
{
|
||||
$this->build_args = collect(["--build-arg SOURCE_COMMIT={$this->commit}"]);
|
||||
$this->generate_env_variables();
|
||||
$nixpacks_command = "nixpacks build -o {$this->workdir} {$this->env_args} --no-error-without-start";
|
||||
if ($this->application->build_command) {
|
||||
$nixpacks_command .= " --build-cmd \"{$this->application->build_command}\"";
|
||||
}
|
||||
if ($this->application->start_command) {
|
||||
$nixpacks_command .= " --start-cmd \"{$this->application->start_command}\"";
|
||||
}
|
||||
if ($this->application->install_command) {
|
||||
$nixpacks_command .= " --install-cmd \"{$this->application->install_command}\"";
|
||||
}
|
||||
$nixpacks_command .= " {$this->workdir}";
|
||||
return $this->execute_in_builder($nixpacks_command);
|
||||
}
|
||||
|
||||
private function generate_env_variables()
|
||||
{
|
||||
$this->env_args = collect([]);
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$this->build_args->push("--build-arg {$env->key}={$env->value}");
|
||||
foreach ($this->application->nixpacks_environment_variables as $env) {
|
||||
$this->env_args->push("--env {$env->key}={$env->value}");
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||
$this->build_args->push("--build-arg {$env->key}={$env->value}");
|
||||
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
|
||||
$this->env_args->push("--env {$env->key}={$env->value}");
|
||||
}
|
||||
}
|
||||
|
||||
$this->build_args = $this->build_args->implode(' ');
|
||||
$this->env_args = $this->env_args->implode(' ');
|
||||
}
|
||||
|
||||
private function generate_compose_file()
|
||||
@ -388,6 +371,7 @@ private function generate_compose_file()
|
||||
$docker_compose_base64 = base64_encode($this->docker_compose);
|
||||
$this->execute_remote_command([$this->execute_in_builder("echo '{$docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
@ -400,6 +384,7 @@ private function generate_local_persistent_volumes()
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes_only_volume_names()
|
||||
{
|
||||
$local_persistent_volumes_names = [];
|
||||
@ -420,6 +405,7 @@ private function generate_local_persistent_volumes_only_volume_names()
|
||||
}
|
||||
return $local_persistent_volumes_names;
|
||||
}
|
||||
|
||||
private function generate_environment_variables($ports)
|
||||
{
|
||||
$environment_variables = collect();
|
||||
@ -436,27 +422,12 @@ private function generate_environment_variables($ports)
|
||||
}
|
||||
}
|
||||
// Add PORT if not exists, use the first port as default
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
|
||||
$environment_variables->push("PORT={$ports[0]}");
|
||||
}
|
||||
return $environment_variables->all();
|
||||
}
|
||||
private function generate_healthcheck_commands()
|
||||
{
|
||||
if (!$this->application->health_check_port) {
|
||||
$this->application->health_check_port = $this->application->ports_exposes_array[0];
|
||||
}
|
||||
if ($this->application->health_check_path) {
|
||||
$generated_healthchecks_commands = [
|
||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}{$this->application->health_check_path} > /dev/null"
|
||||
];
|
||||
} else {
|
||||
$generated_healthchecks_commands = [
|
||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}/"
|
||||
];
|
||||
}
|
||||
return implode(' ', $generated_healthchecks_commands);
|
||||
}
|
||||
|
||||
private function set_labels_for_applications()
|
||||
{
|
||||
$labels = [];
|
||||
@ -520,140 +491,192 @@ private function set_labels_for_applications()
|
||||
}
|
||||
return $labels;
|
||||
}
|
||||
private function generate_buildpack()
|
||||
|
||||
private function generate_healthcheck_commands()
|
||||
{
|
||||
if (!$this->application->health_check_port) {
|
||||
$this->application->health_check_port = $this->application->ports_exposes_array[0];
|
||||
}
|
||||
if ($this->application->health_check_path) {
|
||||
$generated_healthchecks_commands = [
|
||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}{$this->application->health_check_path} > /dev/null"
|
||||
];
|
||||
} else {
|
||||
$generated_healthchecks_commands = [
|
||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}/"
|
||||
];
|
||||
}
|
||||
return implode(' ', $generated_healthchecks_commands);
|
||||
}
|
||||
|
||||
private function build_image()
|
||||
{
|
||||
$this->execute_remote_command([
|
||||
"echo -n 'Building docker image.'",
|
||||
]);
|
||||
|
||||
if ($this->application->settings->is_static) {
|
||||
$this->execute_remote_command([
|
||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
|
||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||
WORKDIR /usr/share/nginx/html/
|
||||
LABEL coolify.deploymentId={$this->deployment_uuid}
|
||||
COPY --from=$this->build_image_name /app/{$this->application->publish_directory} .
|
||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
|
||||
$nginx_config = base64_encode("server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files \$uri \$uri.html \$uri/index.html \$uri/ /index.html =404;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
$this->execute_in_builder("echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile-prod")
|
||||
],
|
||||
[
|
||||
$this->execute_in_builder("echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
|
||||
],
|
||||
[
|
||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command([
|
||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function stop_running_container()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo -n 'Generating nixpacks configuration.'",
|
||||
],
|
||||
[$this->nixpacks_build_cmd()],
|
||||
[$this->execute_in_builder("cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
|
||||
[$this->execute_in_builder("rm -f {$this->workdir}/.nixpacks/Dockerfile")]
|
||||
["echo -n 'Removing old running application.'"],
|
||||
[$this->execute_in_builder("docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
private function nixpacks_build_cmd()
|
||||
|
||||
private function start_by_compose_file()
|
||||
{
|
||||
$this->generate_env_variables();
|
||||
$nixpacks_command = "nixpacks build -o {$this->workdir} {$this->env_args} --no-error-without-start";
|
||||
if ($this->application->build_command) {
|
||||
$nixpacks_command .= " --build-cmd \"{$this->application->build_command}\"";
|
||||
}
|
||||
if ($this->application->start_command) {
|
||||
$nixpacks_command .= " --start-cmd \"{$this->application->start_command}\"";
|
||||
}
|
||||
if ($this->application->install_command) {
|
||||
$nixpacks_command .= " --install-cmd \"{$this->application->install_command}\"";
|
||||
}
|
||||
$nixpacks_command .= " {$this->workdir}";
|
||||
return $this->execute_in_builder($nixpacks_command);
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting new application... '"],
|
||||
[$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
|
||||
["echo 'Done. 🎉'"],
|
||||
);
|
||||
}
|
||||
private function generate_env_variables()
|
||||
|
||||
private function deploy()
|
||||
{
|
||||
$this->env_args = collect([]);
|
||||
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
|
||||
],
|
||||
);
|
||||
$this->prepare_builder_image();
|
||||
$this->clone_repository();
|
||||
|
||||
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
||||
if (strlen($tag) > 128) {
|
||||
$tag = $tag->substr(0, 128);
|
||||
};
|
||||
|
||||
$this->build_image_name = "{$this->application->git_repository}:{$tag}-build";
|
||||
$this->production_image_name = "{$this->application->uuid}:{$tag}";
|
||||
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||
|
||||
if (!$this->force_rebuild) {
|
||||
$this->execute_remote_command([
|
||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||
]);
|
||||
if (Str::of($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||
$this->execute_remote_command([
|
||||
"echo 'Docker Image found locally with the same Git Commit SHA {$this->application->uuid}:{$this->commit}. Build step skipped...'"
|
||||
]);
|
||||
$this->generate_compose_file();
|
||||
$this->stop_running_container();
|
||||
$this->start_by_compose_file();
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->cleanup_git();
|
||||
$this->generate_buildpack();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
$this->stop_running_container();
|
||||
$this->start_by_compose_file();
|
||||
}
|
||||
|
||||
private function generate_build_env_variables()
|
||||
{
|
||||
$this->build_args = collect(["--build-arg SOURCE_COMMIT={$this->commit}"]);
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->nixpacks_environment_variables as $env) {
|
||||
$this->env_args->push("--env {$env->key}={$env->value}");
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$this->build_args->push("--build-arg {$env->key}={$env->value}");
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
|
||||
$this->env_args->push("--env {$env->key}={$env->value}");
|
||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||
$this->build_args->push("--build-arg {$env->key}={$env->value}");
|
||||
}
|
||||
}
|
||||
|
||||
$this->env_args = $this->env_args->implode(' ');
|
||||
$this->build_args = $this->build_args->implode(' ');
|
||||
}
|
||||
private function cleanup_git()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[$this->execute_in_builder("rm -fr {$this->workdir}/.git")],
|
||||
);
|
||||
}
|
||||
private function prepare_builder_image()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo -n 'Pulling latest version of the builder image (ghcr.io/coollabsio/coolify-builder).'",
|
||||
],
|
||||
[
|
||||
"docker run --pull=always -d --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/coollabsio/coolify-builder",
|
||||
"hidden" => true,
|
||||
],
|
||||
[
|
||||
"command" => $this->execute_in_builder("mkdir -p {$this->workdir}")
|
||||
],
|
||||
);
|
||||
}
|
||||
private function set_git_import_settings($git_clone_command)
|
||||
{
|
||||
if ($this->application->git_commit_sha !== 'HEAD') {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1";
|
||||
}
|
||||
if ($this->application->settings->is_git_submodules_enabled) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git submodule update --init --recursive";
|
||||
}
|
||||
if ($this->application->settings->is_git_lfs_enabled) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git lfs pull";
|
||||
}
|
||||
return $git_clone_command;
|
||||
}
|
||||
private function importing_git_repository()
|
||||
{
|
||||
$commands = collect([]);
|
||||
$git_clone_command = "git clone -q -b {$this->application->git_branch}";
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$pr_branch_name = "pr-{$this->pull_request_id}-coolify";
|
||||
}
|
||||
|
||||
if ($this->application->deploymentType() === 'source') {
|
||||
$source_html_url = data_get($this->application, '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'];
|
||||
private function add_build_env_variables_to_dockerfile()
|
||||
{
|
||||
$this->execute_remote_command([
|
||||
$this->execute_in_builder("cat {$this->workdir}/Dockerfile"), "hidden" => true, "save" => 'dockerfile'
|
||||
]);
|
||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||
|
||||
if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
|
||||
if ($this->source->is_public) {
|
||||
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}";
|
||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||
|
||||
$commands->push($this->execute_in_builder($git_clone_command));
|
||||
} else {
|
||||
$github_access_token = generate_github_installation_token($this->source);
|
||||
$commands->push($this->execute_in_builder("git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}"));
|
||||
}
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$commands->push($this->execute_in_builder("cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"));
|
||||
}
|
||||
return $commands->implode(' && ');
|
||||
}
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}");
|
||||
}
|
||||
if ($this->application->deploymentType() === 'deploy_key') {
|
||||
$private_key = base64_encode($this->application->private_key->private_key);
|
||||
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}";
|
||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||
$commands = collect([
|
||||
$this->execute_in_builder("mkdir -p /root/.ssh"),
|
||||
$this->execute_in_builder("echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
|
||||
$this->execute_in_builder("chmod 600 /root/.ssh/id_rsa"),
|
||||
$this->execute_in_builder($git_clone_command)
|
||||
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
||||
$this->execute_remote_command([
|
||||
$this->execute_in_builder("echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/Dockerfile"),
|
||||
"hidden" => true
|
||||
]);
|
||||
}
|
||||
|
||||
private function next(string $status)
|
||||
{
|
||||
// If the deployment is cancelled by the user, don't update the status
|
||||
if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => $status,
|
||||
]);
|
||||
return $commands->implode(' && ');
|
||||
}
|
||||
queue_next_deployment($this->application);
|
||||
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
||||
$this->application->environment->project->team->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
if ($status === ApplicationDeploymentStatus::FAILED->value) {
|
||||
$this->application->environment->project->team->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
}
|
||||
private function clone_repository()
|
||||
|
||||
public function failed(Throwable $exception): void
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}. '"
|
||||
],
|
||||
[
|
||||
$this->importing_git_repository()
|
||||
],
|
||||
[
|
||||
$this->execute_in_builder("cd {$this->workdir} && git rev-parse HEAD"),
|
||||
"hidden" => true,
|
||||
"save" => "git_commit_sha"
|
||||
],
|
||||
["echo 'Oops something is not okay, are you okay? 😢'"],
|
||||
["echo '{$exception->getMessage()}'"]
|
||||
);
|
||||
$this->commit = $this->saved_outputs->get('git_commit_sha');
|
||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
||||
}
|
||||
}
|
@ -22,11 +22,13 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue
|
||||
|
||||
public function __construct(
|
||||
public string $application_id,
|
||||
public int $pull_request_id,
|
||||
public int $pull_request_id,
|
||||
public string $deployment_uuid,
|
||||
public string $status
|
||||
) {
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
@ -61,6 +63,7 @@ public function handle()
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function update_comment()
|
||||
{
|
||||
['data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'patch', data: [
|
||||
@ -71,6 +74,7 @@ private function update_comment()
|
||||
$this->create_comment();
|
||||
}
|
||||
}
|
||||
|
||||
private function create_comment()
|
||||
{
|
||||
['data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
|
||||
|
@ -3,15 +3,11 @@
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\License\CheckResaleLicense;
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class CheckResaleLicenseJob implements ShouldQueue
|
||||
{
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@ -11,7 +10,6 @@
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ContainerStatusJob implements ShouldQueue, ShouldBeUnique
|
||||
{
|
||||
@ -27,10 +25,12 @@ public function __construct($resource, string $container_name, string|null $pull
|
||||
$this->container_name = $container_name;
|
||||
$this->pull_request_id = $pull_request_id;
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return $this->container_name;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
|
@ -19,8 +19,9 @@ class CoolifyTask implements ShouldQueue
|
||||
*/
|
||||
public function __construct(
|
||||
public Activity $activity,
|
||||
public bool $ignore_errors = false,
|
||||
) {
|
||||
public bool $ignore_errors = false,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,13 +8,14 @@
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DockerCleanupJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 500;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
|
@ -15,10 +15,12 @@ class InstanceApplicationsStatusJob implements ShouldQueue, ShouldBeUnique
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $applications;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->applications = Application::all();
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
|
@ -3,9 +3,6 @@
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Server\UpdateCoolify;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@ -22,6 +19,7 @@ class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique
|
||||
public function __construct(private bool $force = false)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
resolve(UpdateCoolify::class)($this->force);
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@ -18,6 +17,7 @@ class ProxyCheckJob implements ShouldQueue
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
|
@ -9,8 +9,8 @@
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ProxyContainerStatusJob implements ShouldQueue, ShouldBeUnique
|
||||
@ -20,18 +20,22 @@ class ProxyContainerStatusJob implements ShouldQueue, ShouldBeUnique
|
||||
public Server $server;
|
||||
public $tries = 1;
|
||||
public $timeout = 120;
|
||||
public function middleware(): array
|
||||
{
|
||||
return [new WithoutOverlapping($this->server->id)];
|
||||
}
|
||||
|
||||
public function __construct(Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
}
|
||||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [new WithoutOverlapping($this->server->id)];
|
||||
}
|
||||
|
||||
public function uniqueId(): int
|
||||
{
|
||||
return $this->server->id;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
|
@ -17,6 +17,7 @@ class ProxyStartJob implements ShouldQueue
|
||||
public function __construct(protected Server $server)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
@ -29,7 +28,8 @@ class SendMessageToDiscordJob implements ShouldQueue
|
||||
public function __construct(
|
||||
public string $text,
|
||||
public string $webhookUrl
|
||||
) {
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,28 +2,12 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Masmerise\Toaster\Toastable;
|
||||
use Masmerise\Toaster\Toaster;
|
||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class Application extends BaseModel
|
||||
{
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($application) {
|
||||
ApplicationSetting::create([
|
||||
'application_id' => $application->id,
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($application) {
|
||||
$application->settings()->delete();
|
||||
$application->persistentStorages()->delete();
|
||||
});
|
||||
}
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'repository_project_id',
|
||||
@ -43,15 +27,42 @@ protected static function booted()
|
||||
'publish_directory',
|
||||
'private_key_id'
|
||||
];
|
||||
public function type() {
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($application) {
|
||||
ApplicationSetting::create([
|
||||
'application_id' => $application->id,
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($application) {
|
||||
$application->settings()->delete();
|
||||
$application->persistentStorages()->delete();
|
||||
});
|
||||
}
|
||||
|
||||
public function settings()
|
||||
{
|
||||
return $this->hasOne(ApplicationSetting::class);
|
||||
}
|
||||
|
||||
public function persistentStorages()
|
||||
{
|
||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function type()
|
||||
{
|
||||
return 'application';
|
||||
}
|
||||
|
||||
public function publishDirectory(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value ? '/' . ltrim($value, '/') : null,
|
||||
set: fn($value) => $value ? '/' . ltrim($value, '/') : null,
|
||||
);
|
||||
}
|
||||
|
||||
public function gitBranchLocation(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
@ -63,6 +74,7 @@ public function gitBranchLocation(): Attribute
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
public function gitCommits(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
@ -73,99 +85,108 @@ public function gitCommits(): Attribute
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function baseDirectory(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => '/' . ltrim($value, '/'),
|
||||
set: fn($value) => '/' . ltrim($value, '/'),
|
||||
);
|
||||
}
|
||||
|
||||
public function portsMappings(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value === "" ? null : $value,
|
||||
set: fn($value) => $value === "" ? null : $value,
|
||||
);
|
||||
}
|
||||
|
||||
// Normal Deployments
|
||||
|
||||
public function portsMappingsArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () =>
|
||||
is_null($this->ports_mappings)
|
||||
get: fn() => is_null($this->ports_mappings)
|
||||
? []
|
||||
: explode(',', $this->ports_mappings),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
public function portsExposesArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () =>
|
||||
is_null($this->ports_exposes)
|
||||
get: fn() => is_null($this->ports_exposes)
|
||||
? []
|
||||
: explode(',', $this->ports_exposes)
|
||||
);
|
||||
}
|
||||
// Normal Deployments
|
||||
|
||||
public function environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false);
|
||||
}
|
||||
|
||||
public function runtime_environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('key', 'not like', 'NIXPACKS_%');
|
||||
}
|
||||
|
||||
// Preview Deployments
|
||||
|
||||
public function build_environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('is_build_time', true)->where('key', 'not like', 'NIXPACKS_%');
|
||||
}
|
||||
|
||||
public function nixpacks_environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('key', 'like', 'NIXPACKS_%');
|
||||
}
|
||||
// Preview Deployments
|
||||
|
||||
public function environment_variables_preview(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true);
|
||||
}
|
||||
|
||||
public function runtime_environment_variables_preview(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('key', 'not like', 'NIXPACKS_%');
|
||||
}
|
||||
|
||||
public function build_environment_variables_preview(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('is_build_time', true)->where('key', 'not like', 'NIXPACKS_%');
|
||||
}
|
||||
|
||||
public function nixpacks_environment_variables_preview(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('key', 'like', 'NIXPACKS_%');
|
||||
}
|
||||
|
||||
public function private_key()
|
||||
{
|
||||
return $this->belongsTo(PrivateKey::class);
|
||||
}
|
||||
|
||||
public function environment()
|
||||
{
|
||||
return $this->belongsTo(Environment::class);
|
||||
}
|
||||
|
||||
public function previews()
|
||||
{
|
||||
return $this->hasMany(ApplicationPreview::class);
|
||||
}
|
||||
public function settings()
|
||||
{
|
||||
return $this->hasOne(ApplicationSetting::class);
|
||||
}
|
||||
|
||||
public function destination()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function source()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
public function persistentStorages()
|
||||
{
|
||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function deployments(int $skip = 0, int $take = 10)
|
||||
{
|
||||
@ -177,10 +198,12 @@ public function deployments(int $skip = 0, int $take = 10)
|
||||
'deployments' => $deployments
|
||||
];
|
||||
}
|
||||
|
||||
public function get_deployment(string $deployment_uuid)
|
||||
{
|
||||
return Activity::where('subject_id', $this->id)->where('properties->type_uuid', '=', $deployment_uuid)->first();
|
||||
}
|
||||
|
||||
public function isDeployable(): bool
|
||||
{
|
||||
if ($this->settings->is_auto_deploy_enabled) {
|
||||
@ -188,6 +211,7 @@ public function isDeployable(): bool
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isPRDeployable(): bool
|
||||
{
|
||||
if ($this->settings->is_preview_deployments_enabled) {
|
||||
@ -195,6 +219,7 @@ public function isPRDeployable(): bool
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deploymentType()
|
||||
{
|
||||
if (data_get($this, 'private_key_id')) {
|
||||
|
@ -13,12 +13,14 @@ class ApplicationPreview extends BaseModel
|
||||
'status',
|
||||
'application_id',
|
||||
];
|
||||
public function application()
|
||||
{
|
||||
return $this->belongsTo(Application::class);
|
||||
}
|
||||
|
||||
static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id)
|
||||
{
|
||||
return self::where('application_id', $application_id)->where('pull_request_id', $pull_request_id)->firstOrFail();
|
||||
}
|
||||
|
||||
public function application()
|
||||
{
|
||||
return $this->belongsTo(Application::class);
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ class ApplicationSetting extends Model
|
||||
'is_git_submodules_enabled',
|
||||
'is_git_lfs_enabled',
|
||||
];
|
||||
|
||||
public function isStatic(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
@ -42,6 +43,7 @@ public function isStatic(): Attribute
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function application()
|
||||
{
|
||||
return $this->belongsTo(Application::class);
|
||||
|
@ -14,7 +14,7 @@ protected static function boot()
|
||||
static::creating(function (Model $model) {
|
||||
// Generate a UUID if one isn't set
|
||||
if (!$model->uuid) {
|
||||
$model->uuid = (string) new Cuid2(7);
|
||||
$model->uuid = (string)new Cuid2(7);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ public function environment()
|
||||
{
|
||||
return $this->belongsTo(Environment::class);
|
||||
}
|
||||
|
||||
public function destination()
|
||||
{
|
||||
return $this->morphTo();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user