Merge pull request #1612 from coollabsio/next

v4.0.0-beta.184
This commit is contained in:
Andras Bacsai 2024-01-09 14:58:01 +01:00 committed by GitHub
commit 36931b5b18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
145 changed files with 1996 additions and 1484 deletions

View File

@ -2,11 +2,13 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Events\TestEvent;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\S3Storage; use App\Models\S3Storage;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use App\Models\TeamInvitation; use App\Models\TeamInvitation;
use App\Models\User; use App\Models\User;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController; use Illuminate\Routing\Controller as BaseController;
@ -14,11 +16,55 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse;
use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse;
use Illuminate\Support\Facades\Password;
class Controller extends BaseController class Controller extends BaseController
{ {
use AuthorizesRequests, ValidatesRequests; use AuthorizesRequests, ValidatesRequests;
public function realtime_test() {
if (auth()->user()?->currentTeam()->id !== 0) {
return redirect(RouteServiceProvider::HOME);
}
TestEvent::dispatch();
return 'Look at your other tab.';
}
public function verify() {
return view('auth.verify-email');
}
public function email_verify() {
request()->fulfill();
$name = request()->user()?->name;
send_internal_notification("User {$name} verified their email address.");
return redirect(RouteServiceProvider::HOME);
}
public function forgot_password() {
if (is_transactional_emails_active()) {
$arrayOfRequest = request()->only(Fortify::email());
request()->merge([
'email' => Str::lower($arrayOfRequest['email']),
]);
$type = set_transanctional_email_settings();
if (!$type) {
return response()->json(['message' => 'Transactional emails are not active'], 400);
}
request()->validate([Fortify::email() => 'required|email']);
$status = Password::broker(config('fortify.passwords'))->sendResetLink(
request()->only(Fortify::email())
);
if ($status == Password::RESET_LINK_SENT) {
return app(SuccessfulPasswordResetLinkRequestResponse::class, ['status' => $status]);
}
if ($status == Password::RESET_THROTTLED) {
return response('Already requested a password reset in the past minutes.', 400);
}
return app(FailedPasswordResetLinkRequestResponse::class, ['status' => $status]);
}
return response()->json(['message' => 'Transactional emails are not active'], 400);
}
public function link() public function link()
{ {
$token = request()->get('token'); $token = request()->get('token');
@ -51,90 +97,7 @@ class Controller extends BaseController
return redirect()->route('login')->with('error', 'Invalid credentials.'); return redirect()->route('login')->with('error', 'Invalid credentials.');
} }
public function license() public function accept_invitation()
{
if (!isCloud()) {
abort(404);
}
return view('settings.license', [
'settings' => InstanceSettings::get(),
]);
}
public function force_passoword_reset()
{
return view('auth.force-password-reset');
}
public function boarding()
{
if (currentTeam()->boarding || isDev()) {
return view('boarding');
} else {
return redirect()->route('dashboard');
}
}
public function settings()
{
if (isInstanceAdmin()) {
$settings = InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first();
if ($database) {
if ($database->status !== 'running') {
$database->status = 'running';
$database->save();
}
$s3s = S3Storage::whereTeamId(0)->get();
}
return view('settings.configuration', [
'settings' => $settings,
'database' => $database,
's3s' => $s3s ?? [],
]);
} else {
return redirect()->route('dashboard');
}
}
public function team()
{
$invitations = [];
if (auth()->user()->isAdminFromSession()) {
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
}
return view('team.index', [
'invitations' => $invitations,
]);
}
public function storages()
{
$s3 = S3Storage::ownedByCurrentTeam()->get();
return view('team.storages.all', [
's3' => $s3,
]);
}
public function storages_show()
{
$storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->firstOrFail();
return view('team.storages.show', [
'storage' => $storage,
]);
}
public function members()
{
$invitations = [];
if (auth()->user()->isAdminFromSession()) {
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
}
return view('team.members', [
'invitations' => $invitations,
]);
}
public function acceptInvitation()
{ {
try { try {
$resetPassword = request()->query('reset-password'); $resetPassword = request()->query('reset-password');
@ -169,7 +132,7 @@ class Controller extends BaseController
} }
} }
public function revokeInvitation() public function revoke_invitation()
{ {
try { try {
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail(); $invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();

View File

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

View File

@ -30,6 +30,7 @@ use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Throwable; use Throwable;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
use Yosymfony\Toml\Toml;
class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
{ {
@ -73,6 +74,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private $env_args; private $env_args;
private $docker_compose; private $docker_compose;
private $docker_compose_base64; private $docker_compose_base64;
private ?string $nixpacks_plan = null;
private ?string $nixpacks_type = null;
private string $dockerfile_location = '/Dockerfile'; private string $dockerfile_location = '/Dockerfile';
private string $docker_compose_location = '/docker-compose.yml'; private string $docker_compose_location = '/docker-compose.yml';
private ?string $docker_compose_custom_start_command = null; private ?string $docker_compose_custom_start_command = null;
@ -347,6 +350,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->generate_image_names(); $this->generate_image_names();
$this->check_image_locally_or_remotely(); $this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) { if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
$this->execute_remote_command([
"echo 'Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.'",
]);
$this->create_workdir(); $this->create_workdir();
$this->generate_compose_file(); $this->generate_compose_file();
$this->rolling_update(); $this->rolling_update();
@ -579,7 +585,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->generate_nixpacks_confs(); $this->generate_nixpacks_confs();
$this->generate_compose_file(); $this->generate_compose_file();
$this->generate_build_env_variables(); $this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile(); // $this->add_build_env_variables_to_dockerfile();
$this->build_image(); $this->build_image();
$this->rolling_update(); $this->rolling_update();
} }
@ -601,6 +607,24 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->rolling_update(); $this->rolling_update();
} }
private function framework_based_notification()
{
// Laravel old env variables
if ($this->pull_request_id === 0) {
$nixpacks_php_fallback_path = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
$nixpacks_php_root_dir = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
} else {
$nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
}
if ($nixpacks_php_fallback_path?->value === '/index.php' && $nixpacks_php_root_dir?->value === '/app/public' && $this->newVersionIsHealthy === false) {
$this->execute_remote_command(
[
"echo 'There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/frameworks/laravel#requirements'", 'type' => 'err'
],
);
}
}
private function rolling_update() private function rolling_update()
{ {
if ($this->server->isSwarm()) { if ($this->server->isSwarm()) {
@ -637,6 +661,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue->addLogEntry("Rolling update completed."); $this->application_deployment_queue->addLogEntry("Rolling update completed.");
} }
} }
$this->framework_based_notification();
} }
private function health_check() private function health_check()
{ {
@ -676,7 +701,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"echo 'Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}'" "echo 'Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}'"
], ],
); );
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) { if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'healthy') {
$this->newVersionIsHealthy = true; $this->newVersionIsHealthy = true;
$this->application->update(['status' => 'running']); $this->application->update(['status' => 'running']);
$this->execute_remote_command( $this->execute_remote_command(
@ -686,6 +711,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
); );
break; break;
} }
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') {
$this->newVersionIsHealthy = false;
break;
}
$counter++; $counter++;
sleep($this->application->health_check_interval); sleep($this->application->health_check_interval);
} }
@ -870,20 +899,28 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
[ [
"echo -n 'Generating nixpacks configuration with: $nixpacks_command'", "echo -n 'Generating nixpacks configuration with: $nixpacks_command'",
], ],
[executeInDocker($this->deployment_uuid, $nixpacks_command)], [executeInDocker($this->deployment_uuid, $nixpacks_command), "save" => "nixpacks_plan", "hidden" => true],
[executeInDocker($this->deployment_uuid, "cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")], [executeInDocker($this->deployment_uuid, "nixpacks detect {$this->workdir}"), "save" => "nixpacks_type", "hidden" => true],
[executeInDocker($this->deployment_uuid, "rm -f {$this->workdir}/.nixpacks/Dockerfile")]
); );
if ($this->saved_outputs->get('nixpacks_type')) {
$this->nixpacks_type = $this->saved_outputs->get('nixpacks_type');
}
if ($this->saved_outputs->get('nixpacks_plan')) {
$this->nixpacks_plan = $this->saved_outputs->get('nixpacks_plan');
if ($this->nixpacks_plan) {
$parsed = Toml::Parse($this->nixpacks_plan);
// Do any modifications here
$cmds = collect(data_get($parsed, 'phases.setup.cmds', []));
data_set($parsed, 'phases.setup.cmds', $cmds);
$this->nixpacks_plan = json_encode($parsed);
}
}
} }
private function nixpacks_build_cmd() private function nixpacks_build_cmd()
{ {
$this->generate_env_variables(); $this->generate_env_variables();
// $cacheKey = $this->application->uuid; $nixpacks_command = "nixpacks plan -f toml {$this->env_args}";
// if ($this->pull_request_id !== 0) {
// $cacheKey = "{$this->application->uuid}-pr-{$this->pull_request_id}";
// }
$nixpacks_command = "nixpacks build {$this->env_args} --no-error-without-start";
if ($this->application->build_command) { if ($this->application->build_command) {
$nixpacks_command .= " --build-cmd \"{$this->application->build_command}\""; $nixpacks_command .= " --build-cmd \"{$this->application->build_command}\"";
} }
@ -893,7 +930,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->application->install_command) { if ($this->application->install_command) {
$nixpacks_command .= " --install-cmd \"{$this->application->install_command}\""; $nixpacks_command .= " --install-cmd \"{$this->application->install_command}\"";
} }
$nixpacks_command .= " -o {$this->workdir} {$this->workdir}"; $nixpacks_command .= " {$this->workdir}";
return $nixpacks_command; return $nixpacks_command;
} }
@ -904,10 +941,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
foreach ($this->application->nixpacks_environment_variables as $env) { foreach ($this->application->nixpacks_environment_variables as $env) {
$this->env_args->push("--env {$env->key}={$env->value}"); $this->env_args->push("--env {$env->key}={$env->value}");
} }
foreach ($this->application->build_environment_variables as $env) {
$this->env_args->push("--env {$env->key}={$env->value}");
}
} else { } else {
foreach ($this->application->nixpacks_environment_variables_preview as $env) { foreach ($this->application->nixpacks_environment_variables_preview as $env) {
$this->env_args->push("--env {$env->key}={$env->value}"); $this->env_args->push("--env {$env->key}={$env->value}");
} }
foreach ($this->application->build_environment_variables_preview as $env) {
$this->env_args->push("--env {$env->key}={$env->value}");
}
} }
$this->env_args = $this->env_args->implode(' '); $this->env_args = $this->env_args->implode(' ');
@ -1231,22 +1274,28 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}"); }");
} else { } else {
if ($this->application->build_pack === 'nixpacks') { if ($this->application->build_pack === 'nixpacks') {
$this->execute_remote_command( $this->nixpacks_plan = base64_encode($this->nixpacks_plan);
[ $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > {$this->workdir}/thegameplan.json"), "hidden" => true]);
executeInDocker($this->deployment_uuid, "cp {$this->workdir}/Dockerfile {$this->workdir}/.nixpacks/Dockerfile")
],
);
}
if ($this->force_rebuild) { if ($this->force_rebuild) {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build --no-cache $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "nixpacks build -c thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), "hidden" => true
]); ]);
} else { } else {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "nixpacks build -c thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), "hidden" => true
]); ]);
} }
// } } else {
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
]);
} else {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
]);
}
}
$dockerfile = base64_encode("FROM {$this->application->static_image} $dockerfile = base64_encode("FROM {$this->application->static_image}
WORKDIR /usr/share/nginx/html/ WORKDIR /usr/share/nginx/html/
@ -1279,33 +1328,39 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf") executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
], ],
[ [
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"), "hidden" => true
] ]
); );
} else { } else {
// Pure Dockerfile based deployment // Pure Dockerfile based deployment
if ($this->application->dockerfile) { if ($this->application->dockerfile) {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build --pull $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"), "hidden" => true
]); ]);
} else { } else {
if ($this->application->build_pack === 'nixpacks') { if ($this->application->build_pack === 'nixpacks') {
$this->execute_remote_command( $this->nixpacks_plan = base64_encode($this->nixpacks_plan);
[ $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > {$this->workdir}/thegameplan.json"), "hidden" => true]);
executeInDocker($this->deployment_uuid, "cp {$this->workdir}/Dockerfile {$this->workdir}/.nixpacks/Dockerfile")
],
);
}
if ($this->force_rebuild) { if ($this->force_rebuild) {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build --no-cache $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "nixpacks build -c thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), "hidden" => true
]); ]);
} else { } else {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "nixpacks build -c thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), "hidden" => true
]); ]);
} }
} else {
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"), "hidden" => true
]);
} else {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"), "hidden" => true
]);
}
}
} }
} }
$this->execute_remote_command([ $this->execute_remote_command([

View File

@ -162,6 +162,10 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
// Notify user that this container should not be there. // Notify user that this container should not be there.
} }
} }
if (data_get($container,'Name') === '/coolify-db') {
$foundDatabases[] = 0;
}
} }
$serviceLabelId = data_get($labels, 'coolify.serviceId'); $serviceLabelId = data_get($labels, 'coolify.serviceId');
if ($serviceLabelId) { if ($serviceLabelId) {
@ -212,7 +216,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
} }
$name = data_get($exitedService, 'name'); $name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn'); $fqdn = data_get($exitedService, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn; $containerName = $name ? "$name, available at $fqdn" : $fqdn;
$projectUuid = data_get($service, 'environment.project.uuid'); $projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid'); $serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name'); $environmentName = data_get($service, 'environment.name');

View File

@ -274,7 +274,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{ {
$this->skipBoarding(); $this->skipBoarding();
return redirect()->route( return redirect()->route(
'project.resources.new', 'project.resource.create',
[ [
'project_uuid' => $this->createdProject->uuid, 'project_uuid' => $this->createdProject->uuid,
'environment_name' => 'production', 'environment_name' => 'production',

View File

@ -0,0 +1,18 @@
<?php
namespace App\Livewire\CommandCenter;
use App\Models\Server;
use Livewire\Component;
class Index extends Component
{
public $servers = [];
public function mount() {
$this->servers = Server::isReachable()->get();
}
public function render()
{
return view('livewire.command-center.index');
}
}

View File

@ -22,6 +22,10 @@ class ForcePasswordReset extends Component
{ {
$this->email = auth()->user()->email; $this->email = auth()->user()->email;
} }
public function render()
{
return view('livewire.force-password-reset')->layout('layouts.simple');
}
public function submit() public function submit()
{ {
try { try {

View File

@ -5,21 +5,19 @@ namespace App\Livewire\Profile;
use Livewire\Attributes\Validate; use Livewire\Attributes\Validate;
use Livewire\Component; use Livewire\Component;
class Form extends Component class Index extends Component
{ {
public int $userId; public int $userId;
public string $email; public string $email;
#[Validate('required')] #[Validate('required')]
public string $name; public string $name;
public function mount() public function mount()
{ {
$this->userId = auth()->user()->id; $this->userId = auth()->user()->id;
$this->name = auth()->user()->name; $this->name = auth()->user()->name;
$this->email = auth()->user()->email; $this->email = auth()->user()->email;
} }
public function submit() public function submit()
{ {
@ -34,4 +32,8 @@ class Form extends Component
return handleError($e, $this); return handleError($e, $this);
} }
} }
public function render()
{
return view('livewire.profile.index');
}
} }

View File

@ -27,7 +27,7 @@ class AddEnvironment extends Component
'project_id' => $this->project->id, 'project_id' => $this->project->id,
]); ]);
return redirect()->route('project.resources', [ return redirect()->route('project.resource.index', [
'project_uuid' => $this->project->uuid, 'project_uuid' => $this->project->uuid,
'environment_name' => $environment->name, 'environment_name' => $environment->name,
]); ]);

View File

@ -4,7 +4,6 @@ namespace App\Livewire\Project\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker;
use Livewire\Component; use Livewire\Component;
class Configuration extends Component class Configuration extends Component

View File

@ -1,15 +1,15 @@
<?php <?php
namespace App\Livewire\Project\Application; namespace App\Livewire\Project\Application\Deployment;
use App\Models\Application; use App\Models\Application;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
class Deployments extends Component class Index extends Component
{ {
public Application $application; public Application $application;
public Array|Collection $deployments = []; public array|Collection $deployments = [];
public int $deployments_count = 0; public int $deployments_count = 0;
public string $current_url; public string $current_url;
public int $skip = 0; public int $skip = 0;
@ -19,11 +19,28 @@ class Deployments extends Component
protected $queryString = ['pull_request_id']; protected $queryString = ['pull_request_id'];
public function mount() public function mount()
{ {
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
if (!$application) {
return redirect()->route('dashboard');
}
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 40);
$this->application = $application;
$this->deployments = $deployments;
$this->deployments_count = $count;
$this->current_url = url()->current(); $this->current_url = url()->current();
$this->show_pull_request_only(); $this->show_pull_request_only();
$this->show_more(); $this->show_more();
} }
private function show_pull_request_only() { private function show_pull_request_only()
{
if ($this->pull_request_id) { if ($this->pull_request_id) {
$this->deployments = $this->deployments->where('pull_request_id', $this->pull_request_id); $this->deployments = $this->deployments->where('pull_request_id', $this->pull_request_id);
} }
@ -57,4 +74,8 @@ class Deployments extends Component
$this->show_pull_request_only(); $this->show_pull_request_only();
$this->show_more(); $this->show_more();
} }
public function render()
{
return view('livewire.project.application.deployment.index');
}
} }

View File

@ -1,35 +1,20 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Livewire\Project\Application\Deployment;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component;
use Illuminate\Foundation\Validation\ValidatesRequests;
class ApplicationController extends Controller class Show extends Component
{ {
use AuthorizesRequests, ValidatesRequests; public Application $application;
public ApplicationDeploymentQueue $application_deployment_queue;
public string $deployment_uuid;
public $isKeepAliveOn = true;
protected $listeners = ['refreshQueue'];
public function deployments() public function mount() {
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
if (!$application) {
return redirect()->route('dashboard');
}
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 40);
return view('project.application.deployments', ['application' => $application, 'deployments' => $deployments, 'deployments_count' => $count]);
}
public function deployment()
{
$deploymentUuid = request()->route('deployment_uuid'); $deploymentUuid = request()->route('deployment_uuid');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
@ -46,7 +31,7 @@ class ApplicationController extends Controller
} }
// $activity = Activity::where('properties->type_uuid', '=', $deploymentUuid)->first(); // $activity = Activity::where('properties->type_uuid', '=', $deploymentUuid)->first();
// if (!$activity) { // if (!$activity) {
// return redirect()->route('project.application.deployments', [ // return redirect()->route('project.application.deployment.index', [
// 'project_uuid' => $project->uuid, // 'project_uuid' => $project->uuid,
// 'environment_name' => $environment->name, // 'environment_name' => $environment->name,
// 'application_uuid' => $application->uuid, // 'application_uuid' => $application->uuid,
@ -54,17 +39,32 @@ class ApplicationController extends Controller
// } // }
$application_deployment_queue = ApplicationDeploymentQueue::where('deployment_uuid', $deploymentUuid)->first(); $application_deployment_queue = ApplicationDeploymentQueue::where('deployment_uuid', $deploymentUuid)->first();
if (!$application_deployment_queue) { if (!$application_deployment_queue) {
return redirect()->route('project.application.deployments', [ return redirect()->route('project.application.deployment.index', [
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,
'environment_name' => $environment->name, 'environment_name' => $environment->name,
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
]); ]);
} }
return view('project.application.deployment', [ $this->application = $application;
'application' => $application, $this->application_deployment_queue = $application_deployment_queue;
// 'activity' => $activity, $this->deployment_uuid = $deploymentUuid;
'application_deployment_queue' => $application_deployment_queue, }
'deployment_uuid' => $deploymentUuid,
]); public function refreshQueue()
{
$this->application_deployment_queue->refresh();
}
public function polling()
{
$this->dispatch('deploymentFinished');
$this->application_deployment_queue->refresh();
if (data_get($this->application_deployment_queue, 'status') == 'finished' || data_get($this->application_deployment_queue, 'status') == 'failed') {
$this->isKeepAliveOn = false;
}
}
public function render()
{
return view('livewire.project.application.deployment.show');
} }
} }

View File

@ -1,27 +0,0 @@
<?php
namespace App\Livewire\Project\Application;
use App\Models\ApplicationDeploymentQueue;
use Livewire\Component;
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->dispatch('deploymentFinished');
$this->application_deployment_queue->refresh();
if (data_get($this->application_deployment_queue, 'status') == 'finished' || data_get($this->application_deployment_queue, 'status') == 'failed') {
$this->isKeepAliveOn = false;
}
}
}

View File

@ -39,7 +39,7 @@ class Heading extends Component
} else { } else {
dispatch(new ServerStatusJob($this->application->destination->server)); dispatch(new ServerStatusJob($this->application->destination->server));
} }
if ($showNotification) $this->dispatch('success', 'Application status updated.'); if ($showNotification) $this->dispatch('success', "Application ({$this->application->name}) status updated.");
} }
public function force_deploy_without_cache() public function force_deploy_without_cache()
@ -60,7 +60,7 @@ class Heading extends Component
force_rebuild: false, force_rebuild: false,
is_new_deployment: true, is_new_deployment: true,
); );
return redirect()->route('project.application.deployment', [ return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid, 'deployment_uuid' => $this->deploymentUuid,
@ -83,7 +83,7 @@ class Heading extends Component
deployment_uuid: $this->deploymentUuid, deployment_uuid: $this->deploymentUuid,
force_rebuild: $force_rebuild, force_rebuild: $force_rebuild,
); );
return redirect()->route('project.application.deployment', [ return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid, 'deployment_uuid' => $this->deploymentUuid,
@ -113,7 +113,7 @@ class Heading extends Component
restart_only: true, restart_only: true,
is_new_deployment: true, is_new_deployment: true,
); );
return redirect()->route('project.application.deployment', [ return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid, 'deployment_uuid' => $this->deploymentUuid,
@ -128,7 +128,7 @@ class Heading extends Component
deployment_uuid: $this->deploymentUuid, deployment_uuid: $this->deploymentUuid,
restart_only: true, restart_only: true,
); );
return redirect()->route('project.application.deployment', [ return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid, 'deployment_uuid' => $this->deploymentUuid,

View File

@ -52,7 +52,7 @@ class Previews extends Component
force_rebuild: true, force_rebuild: true,
pull_request_id: $pull_request_id, pull_request_id: $pull_request_id,
); );
return redirect()->route('project.application.deployment', [ return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deployment_uuid, 'deployment_uuid' => $this->deployment_uuid,

View File

@ -29,7 +29,7 @@ class Rollback extends Component
commit: $commit, commit: $commit,
force_rebuild: false, force_rebuild: false,
); );
return redirect()->route('project.application.deployment', [ return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $deployment_uuid, 'deployment_uuid' => $deployment_uuid,

View File

@ -8,7 +8,7 @@ use App\Models\Server;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
class CloneProject extends Component class CloneMe extends Component
{ {
public string $project_uuid; public string $project_uuid;
public string $environment_name; public string $environment_name;
@ -41,7 +41,7 @@ class CloneProject extends Component
public function render() public function render()
{ {
return view('livewire.project.clone-project'); return view('livewire.project.clone-me');
} }
public function selectServer($server_id, $destination_id) public function selectServer($server_id, $destination_id)
@ -152,7 +152,7 @@ class CloneProject extends Component
} }
$newService->parse(); $newService->parse();
} }
return redirect()->route('project.resources', [ return redirect()->route('project.resource.index', [
'project_uuid' => $newProject->uuid, 'project_uuid' => $newProject->uuid,
'environment_name' => $newEnvironment->name, 'environment_name' => $newEnvironment->name,
]); ]);

View File

@ -0,0 +1,41 @@
<?php
namespace App\Livewire\Project\Database\Backup;
use Livewire\Component;
class Execution extends Component
{
public $database;
public $backup;
public $executions;
public $s3s;
public function mount() {
$backup_uuid = request()->route('backup_uuid');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
$backup = $database->scheduledBackups->where('uuid', $backup_uuid)->first();
if (!$backup) {
return redirect()->route('dashboard');
}
$executions = collect($backup->executions)->sortByDesc('created_at');
$this->database = $database;
$this->backup = $backup;
$this->executions = $executions;
$this->s3s = currentTeam()->s3s;
}
public function render()
{
return view('livewire.project.database.backup.execution');
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Livewire\Project\Database\Backup;
use Livewire\Component;
class Index extends Component
{
public $database;
public $s3s;
public function mount() {
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
// No backups for redis
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'database_uuid' => $database->uuid,
]);
}
$this->database = $database;
$this->s3s = currentTeam()->s3s;
}
public function render()
{
return view('livewire.project.database.backup.index');
}
}

View File

@ -52,7 +52,7 @@ class BackupEdit extends Component
$url = $url->getPath() . "#{$url->getFragment()}"; $url = $url->getPath() . "#{$url->getFragment()}";
return redirect($url); return redirect($url);
} else { } else {
return redirect()->route('project.database.backups.all', $this->parameters); return redirect()->route('project.database.backup.index', $this->parameters);
} }
} }

View File

@ -0,0 +1,29 @@
<?php
namespace App\Livewire\Project\Database;
use Livewire\Component;
class Configuration extends Component
{
public $database;
public function mount() {
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
$this->database = $database;
}
public function render()
{
return view('livewire.project.database.configuration');
}
}

View File

@ -25,6 +25,6 @@ class DeleteProject extends Component
return $this->dispatch('error', 'Project has resources defined, please delete them first.'); return $this->dispatch('error', 'Project has resources defined, please delete them first.');
} }
$project->delete(); $project->delete();
return redirect()->route('projects'); return redirect()->route('project.index');
} }
} }

View File

@ -12,6 +12,15 @@ class Edit extends Component
'project.name' => 'required|min:3|max:255', 'project.name' => 'required|min:3|max:255',
'project.description' => 'nullable|string|max:255', 'project.description' => 'nullable|string|max:255',
]; ];
public function mount() {
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) {
return redirect()->route('dashboard');
}
$this->project = $project;
}
public function submit() public function submit()
{ {

View File

@ -0,0 +1,21 @@
<?php
namespace App\Livewire\Project;
use App\Models\Project;
use App\Models\Server;
use Livewire\Component;
class Index extends Component
{
public $projects;
public $servers;
public function mount() {
$this->projects = Project::ownedByCurrentTeam()->get();
$this->servers = Server::ownedByCurrentTeam()->count();
}
public function render()
{
return view('livewire.project.index');
}
}

View File

@ -55,7 +55,7 @@ class Select extends Component
public function updatedSelectedEnvironment() public function updatedSelectedEnvironment()
{ {
return redirect()->route('project.resources.new', [ return redirect()->route('project.resource.create', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->selectedEnvironment, 'environment_name' => $this->selectedEnvironment,
]); ]);
@ -157,7 +157,7 @@ class Select extends Component
public function setDestination(string $destination_uuid) public function setDestination(string $destination_uuid)
{ {
$this->destination_uuid = $destination_uuid; $this->destination_uuid = $destination_uuid;
return redirect()->route('project.resources.new', [ return redirect()->route('project.resource.create', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
'type' => $this->type, 'type' => $this->type,

View File

@ -1,52 +1,18 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Livewire\Project\Resource;
use App\Models\EnvironmentVariable; use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Server;
use App\Models\Service; use App\Models\Service;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use Illuminate\Support\Str; use Livewire\Component;
class ProjectController extends Controller class Create extends Component
{
public function all()
{
return view('projects', [
'projects' => Project::ownedByCurrentTeam()->get(),
'servers' => Server::ownedByCurrentTeam()->count(),
]);
}
public function edit()
{
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) {
return redirect()->route('dashboard');
}
return view('project.edit', ['project' => $project]);
}
public function show()
{
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) {
return redirect()->route('dashboard');
}
$project->load(['environments']);
return view('project.show', ['project' => $project]);
}
public function new()
{ {
public $type;
public function mount() {
$services = getServiceTemplates(); $services = getServiceTemplates();
$type = Str::of(request()->query('type')); $type = str(request()->query('type'));
$destination_uuid = request()->query('destination'); $destination_uuid = request()->query('destination');
$server_id = request()->query('server_id'); $server_id = request()->query('server_id');
@ -81,14 +47,14 @@ class ProjectController extends Controller
$oneClickService = data_get($services, "$oneClickServiceName.compose"); $oneClickService = data_get($services, "$oneClickServiceName.compose");
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null); $oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
if ($oneClickDotEnvs) { if ($oneClickDotEnvs) {
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) { $oneClickDotEnvs = str(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
return !empty($value); return !empty($value);
}); });
} }
if ($oneClickService) { if ($oneClickService) {
$destination = StandaloneDocker::whereUuid($destination_uuid)->first(); $destination = StandaloneDocker::whereUuid($destination_uuid)->first();
$service = Service::create([ $service = Service::create([
'name' => "$oneClickServiceName-" . Str::random(10), 'name' => "$oneClickServiceName-" . str()->random(10),
'docker_compose_raw' => base64_decode($oneClickService), 'docker_compose_raw' => base64_decode($oneClickService),
'environment_id' => $environment->id, 'environment_id' => $environment->id,
'server_id' => (int) $server_id, 'server_id' => (int) $server_id,
@ -99,8 +65,8 @@ class ProjectController extends Controller
$service->save(); $service->save();
if ($oneClickDotEnvs?->count() > 0) { if ($oneClickDotEnvs?->count() > 0) {
$oneClickDotEnvs->each(function ($value) use ($service) { $oneClickDotEnvs->each(function ($value) use ($service) {
$key = Str::before($value, '='); $key = str()->before($value, '=');
$value = Str::of(Str::after($value, '=')); $value = str(str()->after($value, '='));
$generatedValue = $value; $generatedValue = $value;
if ($value->contains('SERVICE_')) { if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_'); $command = $value->after('SERVICE_')->beforeLast('_');
@ -123,24 +89,10 @@ class ProjectController extends Controller
]); ]);
} }
} }
return view('project.new', [ $this->type = $type->value();
'type' => $type->value()
]);
} }
public function render()
public function resources()
{ {
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); return view('livewire.project.resource.create');
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first();
if (!$environment) {
return redirect()->route('dashboard');
}
return view('project.resources', [
'project' => $project,
'environment' => $environment
]);
} }
} }

View File

@ -0,0 +1,29 @@
<?php
namespace App\Livewire\Project\Resource;
use App\Models\Environment;
use App\Models\Project;
use Livewire\Component;
class Index extends Component
{
public Project $project;
public Environment $environment;
public function mount () {
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first();
if (!$environment) {
return redirect()->route('dashboard');
}
$this->project = $project;
$this->environment = $environment;
}
public function render()
{
return view('livewire.project.resource.index');
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Livewire\Project\Service;
use App\Jobs\ContainerStatusJob;
use App\Models\Service;
use Livewire\Component;
class Configuration extends Component
{
public Service $service;
public $applications;
public $databases;
public array $parameters;
public array $query;
public function getListeners()
{
$userId = auth()->user()->id;
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'checkStatus',
"refreshStacks",
"checkStatus",
];
}
public function render()
{
return view('livewire.project.service.configuration');
}
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
}
public function checkStatus()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStacks();
$this->dispatch('serviceStatusChanged');
}
public function refreshStacks()
{
$this->applications = $this->service->applications->sort();
$this->applications->each(function ($application) {
$application->refresh();
});
$this->databases = $this->service->databases->sort();
$this->databases->each(function ($database) {
$database->refresh();
});
}
}

View File

@ -2,53 +2,51 @@
namespace App\Livewire\Project\Service; namespace App\Livewire\Project\Service;
use App\Jobs\ContainerStatusJob;
use App\Models\Service; use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
class Index extends Component class Index extends Component
{ {
public Service $service; public Service $service;
public $applications; public ?ServiceApplication $serviceApplication = null;
public $databases; public ?ServiceDatabase $serviceDatabase = null;
public array $parameters; public array $parameters;
public array $query; public array $query;
public function getListeners() public Collection $services;
public $s3s;
protected $listeners = ['generateDockerCompose'];
public function mount()
{ {
$userId = auth()->user()->id; try {
return [ $this->services = collect([]);
"echo-private:user.{$userId},ServiceStatusChanged" => 'checkStatus', $this->parameters = get_route_parameters();
"refreshStacks", $this->query = request()->query();
"checkStatus", $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
]; $service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
if ($service) {
$this->serviceApplication = $service;
$this->serviceApplication->getFilesFromServer();
} else {
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
$this->serviceDatabase->getFilesFromServer();
}
$this->s3s = currentTeam()->s3s;
} catch(\Throwable $e) {
return handleError($e, $this);
}
}
public function generateDockerCompose()
{
$this->service->parse();
} }
public function render() public function render()
{ {
return view('livewire.project.service.index'); return view('livewire.project.service.index');
} }
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
}
public function checkStatus()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStacks();
$this->dispatch('serviceStatusChanged');
}
public function refreshStacks()
{
$this->applications = $this->service->applications->sort();
$this->applications->each(function ($application) {
$application->refresh();
});
$this->databases = $this->service->databases->sort();
$this->databases->each(function ($database) {
$database->refresh();
});
}
} }

View File

@ -1,52 +0,0 @@
<?php
namespace App\Livewire\Project\Service;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Support\Collection;
use Livewire\Component;
class Show extends Component
{
public Service $service;
public ?ServiceApplication $serviceApplication = null;
public ?ServiceDatabase $serviceDatabase = null;
public array $parameters;
public array $query;
public Collection $services;
public $s3s;
protected $listeners = ['generateDockerCompose'];
public function mount()
{
try {
$this->services = collect([]);
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
if ($service) {
$this->serviceApplication = $service;
$this->serviceApplication->getFilesFromServer();
} else {
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
$this->serviceDatabase->getFilesFromServer();
}
$this->s3s = currentTeam()->s3s;
} catch(\Throwable $e) {
return handleError($e, $this);
}
}
public function generateDockerCompose()
{
$this->service->parse();
}
public function render()
{
return view('livewire.project.service.show');
}
}

View File

@ -25,7 +25,7 @@ class Danger extends Component
{ {
try { try {
DeleteResourceJob::dispatchSync($this->resource); DeleteResourceJob::dispatchSync($this->resource);
return redirect()->route('project.resources', [ return redirect()->route('project.resource.index', [
'project_uuid' => $this->projectUuid, 'project_uuid' => $this->projectUuid,
'environment_name' => $this->environmentName 'environment_name' => $this->environmentName
]); ]);

View File

@ -10,7 +10,6 @@ use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql; use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis; use App\Models\StandaloneRedis;
use Illuminate\Support\Sleep;
use Livewire\Component; use Livewire\Component;
class ExecuteContainerCommand extends Component class ExecuteContainerCommand extends Component

View File

@ -0,0 +1,26 @@
<?php
namespace App\Livewire\Project;
use App\Models\Project;
use Livewire\Component;
class Show extends Component
{
public Project $project;
public function mount() {
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) {
return redirect()->route('dashboard');
}
$project->load(['environments']);
$this->project = $project;
}
public function render()
{
return view('livewire.project.show');
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Livewire\PrivateKey; namespace App\Livewire\Security\PrivateKey;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use DanHarrin\LivewireRateLimiting\WithRateLimiting; use DanHarrin\LivewireRateLimiting\WithRateLimiting;

View File

@ -1,11 +1,11 @@
<?php <?php
namespace App\Livewire\PrivateKey; namespace App\Livewire\Security\PrivateKey;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use Livewire\Component; use Livewire\Component;
class Change extends Component class Show extends Component
{ {
public PrivateKey $private_key; public PrivateKey $private_key;
public $public_key; public $public_key;
@ -24,6 +24,7 @@ class Change extends Component
public function mount() public function mount()
{ {
try { try {
$this->private_key = PrivateKey::ownedByCurrentTeam(['name', 'description', 'private_key', 'is_git_related'])->whereUuid(request()->private_key_uuid)->firstOrFail();
$this->public_key = $this->private_key->publicKey(); $this->public_key = $this->private_key->publicKey();
}catch(\Throwable $e) { }catch(\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@ -19,7 +19,7 @@ class Delete extends Component
return; return;
} }
$this->server->delete(); $this->server->delete();
return redirect()->route('server.all'); return redirect()->route('server.index');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@ -15,7 +15,7 @@ class Show extends Component
try { try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($this->server)) {
return redirect()->route('server.all'); return redirect()->route('server.index');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@ -6,7 +6,7 @@ use App\Models\Server;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Livewire\Component; use Livewire\Component;
class All extends Component class Index extends Component
{ {
public ?Collection $servers = null; public ?Collection $servers = null;
@ -15,6 +15,6 @@ class All extends Component
} }
public function render() public function render()
{ {
return view('livewire.server.all'); return view('livewire.server.index');
} }
} }

View File

@ -43,7 +43,7 @@ class LogDrains extends Component
try { try {
$server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($server)) { if (is_null($server)) {
return redirect()->route('server.all'); return redirect()->route('server.index');
} }
$this->server = $server; $this->server = $server;
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@ -17,7 +17,7 @@ class Show extends Component
try { try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($this->server)) {
return redirect()->route('server.all'); return redirect()->route('server.index');
} }
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false); $this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false);
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@ -15,7 +15,7 @@ class Logs extends Component
try { try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($this->server)) {
return redirect()->route('server.all'); return redirect()->route('server.index');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@ -20,7 +20,7 @@ class Show extends Component
try { try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($this->server)) {
return redirect()->route('server.all'); return redirect()->route('server.index');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@ -17,7 +17,7 @@ class Show extends Component
try { try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($this->server)) {
return redirect()->route('server.all'); return redirect()->route('server.index');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@ -0,0 +1,38 @@
<?php
namespace App\Livewire\Settings;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\StandalonePostgresql;
use Livewire\Component;
class Index extends Component
{
public InstanceSettings $settings;
public StandalonePostgresql $database;
public $s3s;
public function mount()
{
if (isInstanceAdmin()) {
$settings = InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($database) {
if ($database->status !== 'running') {
$database->status = 'running';
$database->save();
}
$this->database = $database;
}
$this->settings = $settings;
$this->s3s = $s3s;
} else {
return redirect()->route('dashboard');
}
}
public function render()
{
return view('livewire.settings.index');
}
}

View File

@ -1,15 +1,16 @@
<?php <?php
namespace App\Livewire; namespace App\Livewire\Settings;
use App\Actions\License\CheckResaleLicense; use App\Actions\License\CheckResaleLicense;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use Livewire\Component; use Livewire\Component;
class CheckLicense extends Component class License extends Component
{ {
public InstanceSettings|null $settings = null; public InstanceSettings $settings;
public string|null $instance_id = null; public string|null $instance_id = null;
protected $rules = [ protected $rules = [
'settings.resale_license' => 'nullable', 'settings.resale_license' => 'nullable',
'settings.is_resale_license_active' => 'nullable', 'settings.is_resale_license_active' => 'nullable',
@ -20,12 +21,17 @@ class CheckLicense extends Component
'settings.is_resale_license_active' => 'Is License Active', 'settings.is_resale_license_active' => 'Is License Active',
]; ];
public function mount() public function mount () {
{ if (!isCloud()) {
abort(404);
}
$this->instance_id = config('app.id'); $this->instance_id = config('app.id');
$this->settings = InstanceSettings::get(); $this->settings = InstanceSettings::get();
} }
public function render()
{
return view('livewire.settings.license')->layout('layouts.subscription');
}
public function submit() public function submit()
{ {
$this->validate(); $this->validate();

View File

@ -6,7 +6,7 @@ use App\Models\InstanceSettings;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
use Livewire\Component; use Livewire\Component;
class Show extends Component class Index extends Component
{ {
public InstanceSettings $settings; public InstanceSettings $settings;
public bool $alreadySubscribed = false; public bool $alreadySubscribed = false;
@ -26,6 +26,6 @@ class Show extends Component
} }
public function render() public function render()
{ {
return view('livewire.subscription.show')->layout('layouts.subscription'); return view('livewire.subscription.index')->layout('layouts.subscription');
} }
} }

View File

@ -1,29 +0,0 @@
<?php
namespace App\Livewire\Team;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class Delete extends Component
{
public function delete()
{
$currentTeam = currentTeam();
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === auth()->user()->id) {
return;
}
$user->teams()->detach($currentTeam);
$session = DB::table('sessions')->where('user_id', $user->id)->first();
if ($session) {
DB::table('sessions')->where('id', $session->id)->delete();
}
});
refreshSession();
return redirect()->route('team.index');
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace App\Livewire\Team;
use App\Models\Team;
use Livewire\Component;
class Form extends Component
{
public Team $team;
protected $rules = [
'team.name' => 'required|min:3|max:255',
'team.description' => 'nullable|min:3|max:255',
];
protected $validationAttributes = [
'team.name' => 'name',
'team.description' => 'description',
];
public function mount()
{
$this->team = currentTeam();
}
public function submit()
{
$this->validate();
try {
$this->team->save();
refreshSession();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Livewire\Team;
use App\Models\Team;
use App\Models\TeamInvitation;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class Index extends Component
{
public $invitations = [];
public Team $team;
protected $rules = [
'team.name' => 'required|min:3|max:255',
'team.description' => 'nullable|min:3|max:255',
];
protected $validationAttributes = [
'team.name' => 'name',
'team.description' => 'description',
];
public function mount() {
$this->team = currentTeam();
if (auth()->user()->isAdminFromSession()) {
$this->invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
}
}
public function render()
{
return view('livewire.team.index');
}
public function submit()
{
$this->validate();
try {
$this->team->save();
refreshSession();
$this->dispatch('success', 'Team updated successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function delete()
{
$currentTeam = currentTeam();
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === auth()->user()->id) {
return;
}
$user->teams()->detach($currentTeam);
$session = DB::table('sessions')->where('user_id', $user->id)->first();
if ($session) {
DB::table('sessions')->where('id', $session->id)->delete();
}
});
refreshSession();
return redirect()->route('team.index');
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Livewire\Team\Member;
use App\Models\TeamInvitation;
use Livewire\Component;
class Index extends Component
{
public $invitations = [];
public function mount() {
if (auth()->user()->isAdminFromSession()) {
$this->invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
}
}
public function render()
{
return view('livewire.team.member.index');
}
}

View File

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

View File

@ -65,7 +65,7 @@ class Create extends Component
$this->storage->team_id = currentTeam()->id; $this->storage->team_id = currentTeam()->id;
$this->storage->testConnection(); $this->storage->testConnection();
$this->storage->save(); $this->storage->save();
return redirect()->route('team.storages.show', $this->storage->uuid); return redirect()->route('team.storage.show', $this->storage->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@ -43,7 +43,7 @@ class Form extends Component
{ {
try { try {
$this->storage->delete(); $this->storage->delete();
return redirect()->route('team.storages.all'); return redirect()->route('team.storage.index');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@ -0,0 +1,18 @@
<?php
namespace App\Livewire\Team\Storage;
use App\Models\S3Storage;
use Livewire\Component;
class Index extends Component
{
public $s3;
public function mount() {
$this->s3 = S3Storage::ownedByCurrentTeam()->get();
}
public function render()
{
return view('livewire.team.storage.index');
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Livewire\Team\Storage;
use App\Models\S3Storage;
use Livewire\Component;
class Show extends Component
{
public $storage = null;
public function mount()
{
$this->storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->first();
if (!$this->storage) {
abort(404);
}
}
public function render()
{
return view('livewire.team.storage.show');
}
}

View File

@ -755,7 +755,6 @@ class Application extends BaseModel
// if (count($this->ports_mappings_array) > 0) { // if (count($this->ports_mappings_array) > 0) {
// $deployment->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.'); // $deployment->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
$containers = getCurrentApplicationContainerStatus($server, $this->id, $pullRequestId); $containers = getCurrentApplicationContainerStatus($server, $this->id, $pullRequestId);
ray($containers);
// if ($pullRequestId === 0) { // if ($pullRequestId === 0) {
// $containers = $containers->filter(function ($container) use ($containerName) { // $containers = $containers->filter(function ($container) use ($containerName) {
// return data_get($container, 'Names') !== $containerName; // return data_get($container, 'Names') !== $containerName;
@ -867,7 +866,6 @@ class Application extends BaseModel
} else { } else {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base); $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base);
} }
ray($git_clone_command);
if ($exec_in_docker) { if ($exec_in_docker) {
$commands = collect([ $commands = collect([
executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"), executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"),

View File

@ -48,7 +48,7 @@ class S3Storage extends BaseModel
if ($this->unusable_email_sent === false && is_transactional_emails_active()) { if ($this->unusable_email_sent === false && is_transactional_emails_active()) {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject('Coolify: S3 Storage Connection Error'); $mail->subject('Coolify: S3 Storage Connection Error');
$mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('team.storages.show', ['storage_uuid' => $this->uuid])]); $mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('team.storage.show', ['storage_uuid' => $this->uuid])]);
$users = collect([]); $users = collect([]);
$members = $this->team->members()->get(); $members = $this->team->members()->get();
foreach ($members as $user) { foreach ($members as $user) {

View File

@ -27,7 +27,7 @@ class ContainerRestarted extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject("Coolify: A service ({$this->name}) has been restarted automatically on {$this->server->name}"); $mail->subject("Coolify: A resource ({$this->name}) has been restarted automatically on {$this->server->name}");
$mail->view('emails.container-restarted', [ $mail->view('emails.container-restarted', [
'containerName' => $this->name, 'containerName' => $this->name,
'serverName' => $this->server->name, 'serverName' => $this->server->name,
@ -38,12 +38,12 @@ class ContainerRestarted extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Coolify: A service ({$this->name}) has been restarted automatically on {$this->server->name}"; $message = "Coolify: A resource ({$this->name}) has been restarted automatically on {$this->server->name}";
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
$message = "Coolify: A service ({$this->name}) has been restarted automatically on {$this->server->name}"; $message = "Coolify: A resource ({$this->name}) has been restarted automatically on {$this->server->name}";
$payload = [ $payload = [
"message" => $message, "message" => $message,
]; ];

View File

@ -26,7 +26,7 @@ class ContainerStopped extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject("Coolify: A service ({$this->name}) has been stopped on {$this->server->name}"); $mail->subject("Coolify: A resource has been stopped unexpectedly on {$this->server->name}");
$mail->view('emails.container-stopped', [ $mail->view('emails.container-stopped', [
'containerName' => $this->name, 'containerName' => $this->name,
'serverName' => $this->server->name, 'serverName' => $this->server->name,
@ -37,12 +37,12 @@ class ContainerStopped extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Coolify: A service ({$this->name}) has been stopped on {$this->server->name}"; $message = "Coolify: A resource has been stopped unexpectedly on {$this->server->name}";
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
$message = "Coolify: A service ($this->name} has been stopped on {$this->server->name}"; $message = "Coolify: A resource has been stopped unexpectedly on {$this->server->name}";
$payload = [ $payload = [
"message" => $message, "message" => $message,
]; ];

View File

@ -31,7 +31,7 @@ class FortifyServiceProvider extends ServiceProvider
{ {
// First user (root) will be redirected to /settings instead of / on registration. // First user (root) will be redirected to /settings instead of / on registration.
if ($request->user()->currentTeam->id === 0) { if ($request->user()->currentTeam->id === 0) {
return redirect()->route('settings.configuration'); return redirect()->route('settings.index');
} }
return redirect(RouteServiceProvider::HOME); return redirect(RouteServiceProvider::HOME);
} }

View File

@ -226,8 +226,8 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if (is_null($port) && !is_null($onlyPort)) { if (is_null($port) && !is_null($onlyPort)) {
$port = $onlyPort; $port = $onlyPort;
} }
$http_label = "{$uuid}-{$loop}-http"; $http_label = "http-{$loop}-{$uuid}";
$https_label = "{$uuid}-{$loop}-https"; $https_label = "https-{$loop}-{$uuid}";
if ($schema === 'https') { if ($schema === 'https') {
// Set labels for https // Set labels for https
@ -249,6 +249,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
// Set labels for http (redirect to https) // Set labels for http (redirect to https)
$labels->push("traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)"); $labels->push("traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
$labels->push("traefik.http.routers.{$http_label}.entryPoints=http"); $labels->push("traefik.http.routers.{$http_label}.entryPoints=http");
if ($port) {
$labels->push("traefik.http.services.{$http_label}.loadbalancer.server.port=$port");
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
}
if ($is_force_https_enabled) { if ($is_force_https_enabled) {
$labels->push("traefik.http.routers.{$http_label}.middlewares=redirect-to-https"); $labels->push("traefik.http.routers.{$http_label}.middlewares=redirect-to-https");
} }
@ -258,8 +262,8 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.routers.{$http_label}.entryPoints=http"); $labels->push("traefik.http.routers.{$http_label}.entryPoints=http");
$labels->push("traefik.http.routers.{$http_label}.middlewares=gzip"); $labels->push("traefik.http.routers.{$http_label}.middlewares=gzip");
if ($port) { if ($port) {
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
$labels->push("traefik.http.services.{$http_label}.loadbalancer.server.port=$port"); $labels->push("traefik.http.services.{$http_label}.loadbalancer.server.port=$port");
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
} }
if ($path !== '/') { if ($path !== '/') {
$labels->push("traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix"); $labels->push("traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix");
@ -269,16 +273,15 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
} catch (\Throwable $e) { } catch (\Throwable $e) {
continue; continue;
} }
} }
return $labels; return $labels->sort();
} }
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array
{ {
$ports = $application->settings->is_static ? [80] : $application->ports_exposes_array; $ports = $application->settings->is_static ? [80] : $application->ports_exposes_array;
$onlyPort = null; $onlyPort = null;
if (count($ports) === 1) { if (count($ports) > 0) {
$onlyPort = $ports[0]; $onlyPort = $ports[0];
} }
$pull_request_id = data_get($preview, 'pull_request_id', 0); $pull_request_id = data_get($preview, 'pull_request_id', 0);

View File

@ -106,22 +106,6 @@ function generate_default_proxy_configuration(Server $server)
// Global Middlewares // Global Middlewares
"traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https", "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https",
"traefik.http.middlewares.gzip.compress=true", "traefik.http.middlewares.gzip.compress=true",
// https WWW to non-WWW
"traefik.http.middlewares.https-www-to-non-www.redirectregex.regex=^https?://www\\.(.+)",
"traefik.http.middlewares.https-www-to-non-www.redirectregex.replacement=https://\$1",
"traefik.http.middlewares.https-www-to-non-www.redirectregex.permanent=true",
// https Non-WWW to WWW
"traefik.http.middlewares.https-non-www-to-www.redirectregex.regex=^https?://(?:www\\.)?(.+)",
"traefik.http.middlewares.https-non-www-to-www.redirectregex.replacement=https://www.\$\${1}",
"traefik.http.middlewares.https-non-www-to-www.redirectregex.permanent=true",
// http www to non-WWW
"traefik.http.middlewares.http-www-to-non-www.redirectregex.regex=^http://www\\.(.+)",
"traefik.http.middlewares.http-www-to-non-www.redirectregex.replacement=http://\$1",
"traefik.http.middlewares.http-www-to-non-www.redirectregex.permanent=true",
// http Non-WWW to WWW
"traefik.http.middlewares.http-non-www-to-www.redirectregex.regex=^http://(?:www\\.)?(.+)",
"traefik.http.middlewares.http-non-www-to-www.redirectregex.replacement=http://www.\$\${1}",
"traefik.http.middlewares.http-non-www-to-www.redirectregex.permanent=true",
]; ];
$config = [ $config = [
"version" => "3.8", "version" => "3.8",
@ -171,7 +155,7 @@ function generate_default_proxy_configuration(Server $server)
], ],
]; ];
if (isDev()) { if (isDev()) {
$config['services']['traefik']['command'][] = "--log.level=debug"; // $config['services']['traefik']['command'][] = "--log.level=debug";
$config['services']['traefik']['command'][] = "--accesslog.filepath=/traefik/access.log"; $config['services']['traefik']['command'][] = "--accesslog.filepath=/traefik/access.log";
$config['services']['traefik']['command'][] = "--accesslog.bufferingsize=100"; $config['services']['traefik']['command'][] = "--accesslog.bufferingsize=100";
} }

View File

@ -408,7 +408,7 @@ function generateFqdn(Server $server, string $random)
} }
function sslip(Server $server) function sslip(Server $server)
{ {
if (isDev()) { if (isDev() && $server->id === 0) {
return "http://127.0.0.1.sslip.io"; return "http://127.0.0.1.sslip.io";
} }
if ($server->ip === 'host.docker.internal') { if ($server->ip === 'host.docker.internal') {

View File

@ -65,7 +65,7 @@ return [
'driver' => 'redis', 'driver' => 'redis',
'connection' => 'default', 'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'), 'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 300, 'retry_after' => 3600,
'block_for' => null, 'block_for' => null,
'after_commit' => true, 'after_commit' => true,
], ],

View File

@ -7,7 +7,7 @@ return [
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.183', 'release' => '4.0.0-beta.184',
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

View File

@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.183'; return '4.0.0-beta.184';

View File

@ -1,3 +0,0 @@
<x-layout-simple>
<livewire:force-password-reset />
</x-layout-simple>

View File

@ -13,8 +13,8 @@
href="{{ route('project.application.logs', $parameters) }}"> href="{{ route('project.application.logs', $parameters) }}">
<button>Logs</button> <button>Logs</button>
</a> </a>
<a class="{{ request()->routeIs('project.application.deployments') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('project.application.deployment.index') ? 'text-white' : '' }}"
href="{{ route('project.application.deployments', $parameters) }}"> href="{{ route('project.application.deployment.index', $parameters) }}">
<button>Deployments</button> <button>Deployments</button>
</a> </a>
<x-applications.links :application="$application" /> <x-applications.links :application="$application" />

View File

@ -16,8 +16,8 @@
$database->getMorphClass() === 'App\Models\StandaloneMongodb' || $database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
$database->getMorphClass() === 'App\Models\StandaloneMysql' || $database->getMorphClass() === 'App\Models\StandaloneMysql' ||
$database->getMorphClass() === 'App\Models\StandaloneMariadb') $database->getMorphClass() === 'App\Models\StandaloneMariadb')
<a class="{{ request()->routeIs('project.database.backups.all') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('project.database.backup.index') ? 'text-white' : '' }}"
href="{{ route('project.database.backups.all', $parameters) }}"> href="{{ route('project.database.backup.index', $parameters) }}">
<button>Backups</button> <button>Backups</button>
</a> </a>
@endif @endif

View File

@ -14,7 +14,7 @@
clip-rule="evenodd"></path> clip-rule="evenodd"></path>
</svg> </svg>
<a class="text-xs truncate lg:text-sm" <a class="text-xs truncate lg:text-sm"
href="{{ route('project.resources', ['environment_name' => $this->parameters['environment_name'], 'project_uuid' => $this->parameters['project_uuid']]) }}">{{ $this->parameters['environment_name'] }}</a> href="{{ route('project.resource.index', ['environment_name' => $this->parameters['environment_name'], 'project_uuid' => $this->parameters['project_uuid']]) }}">{{ $this->parameters['environment_name'] }}</a>
</div> </div>
</li> </li>
<li> <li>

View File

@ -2,8 +2,8 @@
<h1>Settings</h1> <h1>Settings</h1>
<div class="subtitle">Instance wide settings for Coolify.</div> <div class="subtitle">Instance wide settings for Coolify.</div>
<nav class="navbar-main"> <nav class="navbar-main">
<a class="{{ request()->routeIs('settings.configuration') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('settings.index') ? 'text-white' : '' }}"
href="{{ route('settings.configuration') }}"> href="{{ route('settings.index') }}">
<button>Configuration</button> <button>Configuration</button>
</a> </a>
@if (isCloud()) @if (isCloud())

View File

@ -17,15 +17,15 @@
<a class="{{ request()->routeIs('team.index') ? 'text-white' : '' }}" href="{{ route('team.index') }}"> <a class="{{ request()->routeIs('team.index') ? 'text-white' : '' }}" href="{{ route('team.index') }}">
<button>General</button> <button>General</button>
</a> </a>
<a class="{{ request()->routeIs('team.members') ? 'text-white' : '' }}" href="{{ route('team.members') }}"> <a class="{{ request()->routeIs('team.member.index') ? 'text-white' : '' }}" href="{{ route('team.member.index') }}">
<button>Members</button> <button>Members</button>
</a> </a>
<a class="{{ request()->routeIs('team.storages.all') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('team.storage.index') ? 'text-white' : '' }}"
href="{{ route('team.storages.all') }}"> href="{{ route('team.storage.index') }}">
<button>S3 Storages</button> <button>S3 Storages</button>
</a> </a>
<a class="{{ request()->routeIs('team.notifications') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('team.notification.index') ? 'text-white' : '' }}"
href="{{ route('team.notifications') }}"> href="{{ route('team.notification.index') }}">
<button>Notifications</button> <button>Notifications</button>
</a> </a>
<div class="flex-1"></div> <div class="flex-1"></div>

View File

@ -7,5 +7,4 @@
@endif @endif
[View Deployment Logs]({{ $deployment_url }}) [View Deployment Logs]({{ $deployment_url }})
</x-emails.layout> </x-emails.layout>

View File

@ -1,9 +1,7 @@
<x-emails.layout> <x-emails.layout>
{{ $name }} has been stopped. {{ $name }} has been stopped.
If it was your intention to stop this application, you can ignore this email. If it was your intention to stop this application, you can ignore this email.
If not, [check what is going on]({{ $application_url }}). If not, [check what is going on]({{ $application_url }}).
</x-emails.layout> </x-emails.layout>

View File

@ -4,5 +4,4 @@
### Reason ### Reason
{{ $output }} {{ $output }}
</x-emails.layout> </x-emails.layout>

View File

@ -1,9 +1,7 @@
<x-emails.layout> <x-emails.layout>
We would like to inform you that a {{ config('constants.limits.trial_period') }} days of trial has been added to all We would like to inform you that a {{ config('constants.limits.trial_period') }} days of trial has been added to all subscription plans.
subscription plans.
You can try out Coolify, without payment information for free. If you like it, you can upgrade to a paid plan at any You can try out Coolify, without payment information for free. If you like it, you can upgrade to a paid plan at any time.
time.
[Click here](https://app.coolify.io/subscription) to start your trial. [Click here](https://app.coolify.io/subscription) to start your trial.
</x-emails.layout> </x-emails.layout>

View File

@ -1,13 +1,9 @@
<x-emails.layout> <x-emails.layout>
A resource ({{ $containerName }}) has been restarted automatically on {{ $serverName }}, because it was stopped unexpectedly.
A service ({{ $containerName }}) has been restarted automatically on {{ $serverName }}, because it was stopped
unexpectedly.
@if ($containerName === 'coolify-proxy') @if ($containerName === 'coolify-proxy')
Coolify Proxy should run on your server as you have FQDNs set up in one of your resources. Coolify Proxy should run on your server as you have FQDNs set up in one of your resources.
If you don't want to use Coolify Proxy, please remove FQDN from your resources or set Proxy type to If you don't want to use Coolify Proxy, please remove FQDN from your resources or set Proxy type to Custom(None).
Custom(None).
@endif @endif
</x-emails.layout> </x-emails.layout>

View File

@ -1,9 +1,7 @@
<x-emails.layout> <x-emails.layout>
A resource ({{ $containerName }}) has been stopped unexpectedly on {{ $serverName }}.
A service ({{ $containerName }}) has been stopped unexpectedly on {{ $serverName }}.
@if ($url) @if ($url)
Please check what is going on [here]({{ $url }}). Please check what is going on [here]({{ $url }}).
@endif @endif
</x-emails.layout> </x-emails.layout>

View File

@ -1,10 +1,7 @@
<x-emails.layout> <x-emails.layout>
Your server ({{ $name }}) has high disk usage ({{ $disk_usage }}% used). Threshold is {{ $threshold }}%.
Your server ({{ $name }}) has high disk usage ({{ $disk_usage }}% used). Threshold is
{{ $threshold }}%.
Please cleanup your disk to prevent data-loss. Here are some [tips](https://coolify.io/docs/automated-cleanup). Please cleanup your disk to prevent data-loss. Here are some [tips](https://coolify.io/docs/automated-cleanup).
(You can change the threshold in the Server Settings menu.) (You can change the threshold in the Server Settings menu.)
</x-emails.layout> </x-emails.layout>

View File

@ -1,5 +1,4 @@
<x-emails.layout> <x-emails.layout>
You have been invited to "{{ $team }}" on "{{ config('app.name') }}". You have been invited to "{{ $team }}" on "{{ config('app.name') }}".
Please [click here]({{ $invitation_link }}) to accept the invitation. Please [click here]({{ $invitation_link }}) to accept the invitation.

View File

@ -1,6 +1,5 @@
<x-emails.layout> <x-emails.layout>
Connection could not be establised with one of your S3 Storage ({{ $name }}). Please fix it Connection could not be establised with one of your S3 Storage ({{ $name }}). Please fix it [here]({{ $url }}).
[here]({{ $url }}).
{{ $reason }} {{ $reason }}
</x-emails.layout> </x-emails.layout>

View File

@ -1,12 +1,9 @@
<x-emails.layout> <x-emails.layout>
Coolify cannot connect to your server ({{ $name }}). Please check your server and make sure it is running. Coolify cannot connect to your server ({{ $name }}). Please check your server and make sure it is running.
All automations & integrations are turned off! All automations & integrations are turned off!
IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn onall automations & integrations.
all automations & integrations.
If you have any questions, please contact us. If you have any questions, please contact us.
</x-emails.layout> </x-emails.layout>

View File

@ -1,6 +1,3 @@
<x-emails.layout> <x-emails.layout>
Your server ({{ $name }}) was offline for a while, but it is back online now. All automations & integrationsare turned on again.
Your server ({{ $name }}) was offline for a while, but it is back online now. All automations & integrations
are turned on again.
</x-emails.layout> </x-emails.layout>

View File

@ -2,5 +2,4 @@
Your last invoice has failed to be paid for Coolify Cloud. Your last invoice has failed to be paid for Coolify Cloud.
Please update payment details [here]({{ $stripeCustomerPortal }}). Please update payment details [here]({{ $stripeCustomerPortal }}).
</x-emails.layout> </x-emails.layout>

View File

@ -1,8 +1,5 @@
<x-emails.layout> <x-emails.layout>
Your trial ended. All automations and integrations are disabled for all of your servers. Your trial ended. All automations and integrations are disabled for all of your servers.
Please update payment details [here]({{ $stripeCustomerPortal }}) or in [Coolify Cloud](https://app.coolify.io) to Please update payment details [here]({{ $stripeCustomerPortal }}) or in [Coolify Cloud](https://app.coolify.io) to continue using our services.
continue using our services.
</x-emails.layout> </x-emails.layout>

View File

@ -1,8 +1,5 @@
<x-emails.layout> <x-emails.layout>
Your trial ends soon. Please update payment details [here]({{ $stripeCustomerPortal }}), Your trial ends soon. Please update payment details [here]({{ $stripeCustomerPortal }}),
Your servers & deployed resources will be untouched, but you won't be able to deploy new resources and lost all Your servers & deployed resources will be untouched, but you won't be able to deploy new resources and lost all automations and integrations.
automations and integrations.
</x-emails.layout> </x-emails.layout>

View File

@ -1,5 +1,3 @@
<x-emails.layout> <x-emails.layout>
<br><br>
If you do not like to receive these emails, you can unsubscribe [here]({{ $unsubscribeUrl }}). If you do not like to receive these emails, you can unsubscribe [here]({{ $unsubscribeUrl }}).
</x-emails.layout> </x-emails.layout>

View File

@ -3,8 +3,5 @@
The link will expire in {{ config('constants.waitlist.expiration') }} minutes. The link will expire in {{ config('constants.waitlist.expiration') }} minutes.
You have no idea what [Coolify Cloud](https://coolify.io) is or this waitlist? [Click here]({{ $cancel_url }}) to remove you from the waitlist.
You have no idea what [Coolify Cloud](https://coolify.io) is or this waitlist? [Click here]({{ $cancel_url }}) to
remove you from the waitlist.
</x-emails.layout> </x-emails.layout>

View File

@ -1,24 +0,0 @@
<form wire:submit='submit' class="flex flex-col gap-2">
<div>
@if ($settings->is_resale_license_active)
<div class="text-success">License is active</div>
@else
<div class="text-error">License is not active</div>
@endif
</div>
<div class="flex gap-2">
<x-forms.input type="password" id="settings.resale_license" placeholder="eg: BE558E91-0CC5-4AA2-B1C0-B6403C2988DD"
label="License Key" />
<x-forms.input type="password" id="instance_id" label="Instance Id (do not change this)" disabled />
</div>
<div class="flex gap-2">
<x-forms.button type="submit">
Check License
</x-forms.button>
</div>
@if (session()->has('error'))
<div class="text-error">
{!! session('error') !!}
</div>
@endif
</form>

View File

@ -0,0 +1,12 @@
<div>
<h1>Command Center</h1>
<div class="subtitle">Execute commands on your servers without leaving the browser.</div>
@if ($servers->count() > 0)
<livewire:run-command :servers="$servers" />
@else
<div>
<div>No servers found. Without a server, you won't be able to do much.</div>
<x-use-magic-bar link="/server/new" />
</div>
@endif
</div>

View File

@ -30,7 +30,7 @@
<div class="gap-2 border border-transparent cursor-pointer box group"> <div class="gap-2 border border-transparent cursor-pointer box group">
@if (data_get($project, 'environments.0.name')) @if (data_get($project, 'environments.0.name'))
<a class="flex flex-col flex-1 mx-6 hover:no-underline" <a class="flex flex-col flex-1 mx-6 hover:no-underline"
href="{{ route('project.resources', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}"> href="{{ route('project.resource.index', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<div class="font-bold text-white">{{ $project->name }}</div> <div class="font-bold text-white">{{ $project->name }}</div>
<div class="description"> <div class="description">
{{ $project->description }}</div> {{ $project->description }}</div>
@ -45,7 +45,7 @@
@endif @endif
<div class="flex items-center"> <div class="flex items-center">
<a class="mx-4 rounded group-hover:text-white hover:no-underline" <a class="mx-4 rounded group-hover:text-white hover:no-underline"
href="{{ route('project.resources.new', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}"> href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<span class="font-bold hover:text-warning">+ New Resource</span> <span class="font-bold hover:text-warning">+ New Resource</span>
</a> </a>
<a class="mx-4 rounded group-hover:text-white" <a class="mx-4 rounded group-hover:text-white"

View File

@ -1,52 +0,0 @@
<div x-data="{ showPrivateKey: false }">
<x-modal yesOrNo modalId="deletePrivateKey" modalTitle="Delete Private Key">
<x-slot:modalBody>
<p>This private key will be deleted. It is not reversible. <br>Please think again.</p>
</x-slot:modalBody>
</x-modal>
<form class="flex flex-col gap-2" wire:submit='changePrivateKey'>
<div class="flex items-end gap-2">
<h2>Private Key</h2>
<x-forms.button type="submit">
Save
</x-forms.button>
@if ($private_key->id > 0)
<x-forms.button isError isModal modalId="deletePrivateKey">
Delete
</x-forms.button>
@endif
</div>
<x-forms.input id="private_key.name" label="Name" required />
<x-forms.input id="private_key.description" label="Description" />
<div>
<div class="flex items-end gap-2 py-2 ">
<div class="pl-1 ">Public Key</div>
</div>
<x-forms.input readonly id="public_key" />
<div class="flex items-end gap-2 py-2 ">
<div class="pl-1 ">Private Key <span class='text-helper'>*</span></div>
<div class="text-xs text-white underline cursor-pointer" x-cloak x-show="!showPrivateKey"
x-on:click="showPrivateKey = true">
Edit
</div>
<div class="text-xs text-white underline cursor-pointer" x-cloak x-show="showPrivateKey"
x-on:click="showPrivateKey = false">
Hide
</div>
</div>
@if ($private_key->is_git_related)
<div class="w-48">
<x-forms.checkbox id="private_key.is_git_related" disabled label="Is used by a Git App?" />
</div>
@endif
<div x-cloak x-show="!showPrivateKey">
<x-forms.input allowToPeak="false" type="password" rows="10" id="private_key.private_key" required
disabled />
</div>
<div x-cloak x-show="showPrivateKey">
<x-forms.textarea rows="10" id="private_key.private_key" required />
</div>
</div>
</form>
</div>

View File

@ -1,12 +0,0 @@
<div>
<form wire:submit='submit' class="flex flex-col">
<div class="flex items-center gap-2">
<h2>General</h2>
<x-forms.button type="submit" label="Save">Save</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input id="name" label="Name" required />
<x-forms.input id="email" label="Email" readonly />
</div>
</form>
</div>

View File

@ -0,0 +1,92 @@
<div>
<h1>Profile</h1>
<div class="subtitle ">Your user profile settings.</div>
<form wire:submit='submit' class="flex flex-col">
<div class="flex items-center gap-2">
<h2>General</h2>
<x-forms.button type="submit" label="Save">Save</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input id="name" label="Name" required />
<x-forms.input id="email" label="Email" readonly />
</div>
</form>
<h2 class="py-4">Subscription</h2>
<a href="{{ route('team.index') }}">Check in Team Settings</a>
<h2 class="py-4">Two-factor Authentication</h2>
@if (session('status') == 'two-factor-authentication-enabled')
<div class="mb-4 font-medium">
Please finish configuring two factor authentication below. Read the QR code or enter the secret key
manually.
</div>
<div class="flex flex-col gap-2">
<form action="/user/confirmed-two-factor-authentication" method="POST" class="flex items-end gap-2">
@csrf
<x-forms.input type="number" id="code" label="One-time code" required />
<x-forms.button type="submit">Validate 2FA</x-forms.button>
</form>
<div>
<div>{!! request()->user()->twoFactorQrCodeSvg() !!}</div>
<div x-data="{ showCode: false }" class="py-2">
<template x-if="showCode">
<div class="py-2 ">{!! decrypt(request()->user()->two_factor_secret) !!}</div>
</template>
<x-forms.button x-on:click="showCode = !showCode">Show secret key to manually
enter</x-forms.button>
</div>
</div>
</div>
@elseif(session('status') == 'two-factor-authentication-confirmed')
<div class="mb-4 ">
Two factor authentication confirmed and enabled successfully.
</div>
<div>
<div class="pb-6 ">Here are the recovery codes for your account. Please store them in a secure
location.
</div>
<div class="text-white">
@foreach (request()->user()->recoveryCodes() as $code)
<div>{{ $code }}</div>
@endforeach
</div>
</div>
@else
@if (request()->user()->two_factor_confirmed_at)
<div class="pb-4 "> Two factor authentication is <span class="text-helper">enabled</span>.</div>
<div class="flex gap-2">
<form action="/user/two-factor-authentication" method="POST">
@csrf
@method ('DELETE')
<x-forms.button type="submit">Disable</x-forms.button>
</form>
<form action="/user/two-factor-recovery-codes" method="POST">
@csrf
<x-forms.button type="submit">Regenerate Recovery Codes</x-forms.button>
</form>
</div>
@if (session('status') == 'recovery-codes-generated')
<div>
<div class="py-6 ">Here are the recovery codes for your account. Please store them in a
secure
location.
</div>
<div class="text-white">
@foreach (request()->user()->recoveryCodes() as $code)
<div>{{ $code }}</div>
@endforeach
</div>
</div>
@endif
@else
<form action="/user/two-factor-authentication" method="POST">
@csrf
<x-forms.button type="submit">Configure 2FA</x-forms.button>
</form>
@endif
@endif
@if (session()->has('errors'))
<div class="text-error">
Something went wrong. Please try again.
</div>
@endif
</div>

View File

@ -1,98 +0,0 @@
<div class="pt-4" x-data="{ fullscreen: false, alwaysScroll: false, intervalId: null }">
<livewire:project.application.deployment-navbar :application_deployment_queue="$application_deployment_queue" />
@if (data_get($application_deployment_queue, 'status') === 'in_progress')
<div class="flex items-center gap-1 pt-2 ">Deployment is
<div class="text-warning"> {{ Str::headline(data_get($this->application_deployment_queue, 'status')) }}.
</div>
<x-loading class="loading-ring" />
</div>
{{-- <div class="">Logs will be updated automatically.</div> --}}
@else
<div class="pt-2 ">Deployment is <span
class="text-warning">{{ Str::headline(data_get($application_deployment_queue, 'status')) }}</span>.
</div>
@endif
<div id="screen" :class="fullscreen ? 'fullscreen' : ''">
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
class="relative flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto text-white bg-coolgray-100 scrollbar border-coolgray-300"
:class="fullscreen ? '' : 'max-h-[40rem] border border-dotted rounded'">
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4" x-on:click="makeFullscreen"><svg
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg></button>
<button title="Go Top" x-show="fullscreen" class="fixed top-4 right-28" x-on:click="goTop"> <svg
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m4-10l-4-4M8 9l4-4" />
</svg></button>
<button title="Follow Logs" x-show="fullscreen" :class="alwaysScroll ? 'text-warning' : ''"
class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
</svg></button>
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-8"
x-on:click="makeFullscreen"><svg class="fixed icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none">
<path
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
<path fill="currentColor"
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
</g>
</svg></button>
<div id="logs" class="flex flex-col">
@if (decode_remote_command_output($application_deployment_queue)->count() > 0)
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
<div @class([
'font-mono whitespace-pre-line',
'text-warning' => $line['hidden'],
'text-red-500' => $line['type'] == 'stderr',
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
<br>COMMAND: <br>{{ $line['command'] }} <br><br>OUTPUT:
@endif{{ $line['output'] }}@if ($line['hidden'])
@endif
</div>
@endforeach
@else
<span class="font-mono text-neutral-400">No logs yet.</span>
@endif
</div>
</div>
</div>
<script>
function makeFullscreen() {
this.fullscreen = !this.fullscreen;
if (this.fullscreen === false) {
this.alwaysScroll = false;
clearInterval(this.intervalId);
}
}
function toggleScroll() {
this.alwaysScroll = !this.alwaysScroll;
if (this.alwaysScroll) {
this.intervalId = setInterval(() => {
const screen = document.getElementById('screen');
const logs = document.getElementById('logs');
if (screen.scrollTop !== logs.scrollHeight) {
screen.scrollTop = logs.scrollHeight;
}
}, 100);
} else {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
function goTop() {
this.alwaysScroll = false;
clearInterval(this.intervalId);
const screen = document.getElementById('screen');
screen.scrollTop = 0;
}
</script>
</div>

View File

@ -0,0 +1,105 @@
<div>
<h1>Deployments</h1>
<livewire:project.application.heading :application="$application" />
{{-- <livewire:project.application.deployment.show :application="$application" :deployments="$deployments" :deployments_count="$deployments_count" /> --}}
<div class="flex flex-col gap-2 pb-10" @if ($skip == 0) wire:poll.5000ms='reload_deployments' @endif>
<div class="flex items-end gap-2 pt-4">
<h2>Deployments <span class="text-xs">({{ $deployments_count }})</span></h2>
@if ($show_next)
<x-forms.button wire:click="load_deployments({{ $default_take }})">Next Page
</x-forms.button>
@endif
</div>
<form class="flex items-end gap-2">
<x-forms.input id="pull_request_id" label="Pull Request"></x-forms.input>
<x-forms.button type="submit">Filter</x-forms.button>
</form>
@forelse ($deployments as $deployment)
<a @class([
'bg-coolgray-100 p-2 border-l border-dashed transition-colors hover:no-underline',
'hover:bg-coolgray-200' => data_get($deployment, 'status') === 'queued',
'border-warning hover:bg-warning hover:text-black' =>
data_get($deployment, 'status') === 'in_progress' ||
data_get($deployment, 'status') === 'cancelled-by-user',
'border-error hover:bg-error' =>
data_get($deployment, 'status') === 'failed',
'border-success hover:bg-success' =>
data_get($deployment, 'status') === 'finished',
]) href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}"
class="hover:no-underline">
<div class="flex flex-col justify-start">
<div class="flex gap-1">
{{ $deployment->created_at }} UTC
<span class=" text-warning">></span>
{{ $deployment->status }}
</div>
@if (data_get($deployment, 'pull_request_id'))
<div>
<span class=" text-warning">></span>
Pull Request #{{ data_get($deployment, 'pull_request_id') }}
@if (data_get($deployment, 'is_webhook'))
(Webhook)
@endif
Webhook (SHA
@if (data_get($deployment, 'commit'))
{{ data_get($deployment, 'commit') }})
@else
HEAD)
@endif
</div>
@endif
</div>
<div class="flex flex-col" x-data="elapsedTime('{{ $deployment->deployment_uuid }}', '{{ $deployment->status }}', '{{ $deployment->created_at }}', '{{ $deployment->updated_at }}')">
<div>
@if ($deployment->status !== 'in_progress')
Finished <span x-text="measure_since_started()">0s</span> in
@else
Running for
@endif
<span class="font-bold" x-text="measure_finished_time()">0s</span>
</div>
</div>
</a>
@empty
<div class="">No deployments found</div>
@endforelse
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/relativeTime.js"></script>
<script>
document.addEventListener('alpine:init', () => {
let timers = {};
dayjs.extend(window.dayjs_plugin_utc);
dayjs.extend(window.dayjs_plugin_relativeTime);
Alpine.data('elapsedTime', (uuid, status, created_at, updated_at) => ({
finished_time: 'calculating...',
started_time: 'calculating...',
init() {
if (timers[uuid]) {
clearInterval(timers[uuid]);
}
if (status === 'in_progress') {
timers[uuid] = setInterval(() => {
this.finished_time = dayjs().diff(dayjs.utc(created_at),
'second') + 's'
}, 1000);
} else {
let seconds = dayjs.utc(updated_at).diff(dayjs.utc(created_at), 'second')
this.finished_time = seconds + 's';
}
},
measure_finished_time() {
return this.finished_time;
},
measure_since_started() {
return dayjs.utc(created_at).fromNow();
}
}))
})
</script>
</div>
</div>

View File

@ -0,0 +1,104 @@
<div>
<h1 class="py-0">Deployment</h1>
<livewire:project.application.heading :application="$application" />
<div class="pt-4" x-data="{ fullscreen: false, alwaysScroll: false, intervalId: null }">
<livewire:project.application.deployment-navbar :application_deployment_queue="$application_deployment_queue" />
@if (data_get($application_deployment_queue, 'status') === 'in_progress')
<div class="flex items-center gap-1 pt-2 ">Deployment is
<div class="text-warning"> {{ Str::headline(data_get($this->application_deployment_queue, 'status')) }}.
</div>
<x-loading class="loading-ring" />
</div>
{{-- <div class="">Logs will be updated automatically.</div> --}}
@else
<div class="pt-2 ">Deployment is <span
class="text-warning">{{ Str::headline(data_get($application_deployment_queue, 'status')) }}</span>.
</div>
@endif
<div id="screen" :class="fullscreen ? 'fullscreen' : ''">
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
class="relative flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto text-white bg-coolgray-100 scrollbar border-coolgray-300"
:class="fullscreen ? '' : 'max-h-[40rem] border border-dotted rounded'">
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4"
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg></button>
<button title="Go Top" x-show="fullscreen" class="fixed top-4 right-28" x-on:click="goTop"> <svg
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m4-10l-4-4M8 9l4-4" />
</svg></button>
<button title="Follow Logs" x-show="fullscreen" :class="alwaysScroll ? 'text-warning' : ''"
class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
</svg></button>
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-8"
x-on:click="makeFullscreen"><svg class="fixed icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none">
<path
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
<path fill="currentColor"
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
</g>
</svg></button>
<div id="logs" class="flex flex-col">
@if (decode_remote_command_output($application_deployment_queue)->count() > 0)
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
<div @class([
'font-mono whitespace-pre-line',
'text-warning' => $line['hidden'],
'text-red-500' => $line['type'] == 'stderr',
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
<br>COMMAND: <br>{{ $line['command'] }} <br><br>OUTPUT:
@endif{{ $line['output'] }}@if ($line['hidden'])
@endif
</div>
@endforeach
@else
<span class="font-mono text-neutral-400">No logs yet.</span>
@endif
</div>
</div>
</div>
<script>
function makeFullscreen() {
this.fullscreen = !this.fullscreen;
if (this.fullscreen === false) {
this.alwaysScroll = false;
clearInterval(this.intervalId);
}
}
function toggleScroll() {
this.alwaysScroll = !this.alwaysScroll;
if (this.alwaysScroll) {
this.intervalId = setInterval(() => {
const screen = document.getElementById('screen');
const logs = document.getElementById('logs');
if (screen.scrollTop !== logs.scrollHeight) {
screen.scrollTop = logs.scrollHeight;
}
}, 100);
} else {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
function goTop() {
this.alwaysScroll = false;
clearInterval(this.intervalId);
const screen = document.getElementById('screen');
screen.scrollTop = 0;
}
</script>
</div>
</div>

View File

@ -1,99 +0,0 @@
<div class="flex flex-col gap-2 pb-10" @if ($skip == 0) wire:poll.5000ms='reload_deployments' @endif>
<div class="flex items-end gap-2 pt-4">
<h2>Deployments <span class="text-xs">({{ $deployments_count }})</span></h2>
@if ($show_next)
<x-forms.button wire:click="load_deployments({{ $default_take }})">Next Page
</x-forms.button>
@endif
</div>
<form class="flex items-end gap-2">
<x-forms.input id="pull_request_id" label="Pull Request"></x-forms.input>
<x-forms.button type="submit">Filter</x-forms.button>
</form>
@forelse ($deployments as $deployment)
<a @class([
'bg-coolgray-100 p-2 border-l border-dashed transition-colors hover:no-underline',
'hover:bg-coolgray-200' => data_get($deployment, 'status') === 'queued',
'border-warning hover:bg-warning hover:text-black' =>
data_get($deployment, 'status') === 'in_progress' ||
data_get($deployment, 'status') === 'cancelled-by-user',
'border-error hover:bg-error' =>
data_get($deployment, 'status') === 'failed',
'border-success hover:bg-success' =>
data_get($deployment, 'status') === 'finished',
]) href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}"
class="hover:no-underline">
<div class="flex flex-col justify-start">
<div class="flex gap-1">
{{ $deployment->created_at }} UTC
<span class=" text-warning">></span>
{{ $deployment->status }}
</div>
@if (data_get($deployment, 'pull_request_id'))
<div>
<span class=" text-warning">></span>
Pull Request #{{ data_get($deployment, 'pull_request_id') }}
@if (data_get($deployment, 'is_webhook'))
(Webhook)
@endif
Webhook (SHA
@if (data_get($deployment, 'commit'))
{{ data_get($deployment, 'commit') }})
@else
HEAD)
@endif
</div>
@endif
</div>
<div class="flex flex-col" x-data="elapsedTime('{{ $deployment->deployment_uuid }}', '{{ $deployment->status }}', '{{ $deployment->created_at }}', '{{ $deployment->updated_at }}')">
<div>
@if ($deployment->status !== 'in_progress')
Finished <span x-text="measure_since_started()">0s</span> in
@else
Running for
@endif
<span class="font-bold" x-text="measure_finished_time()">0s</span>
</div>
</div>
</a>
@empty
<div class="">No deployments found</div>
@endforelse
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/relativeTime.js"></script>
<script>
document.addEventListener('alpine:init', () => {
let timers = {};
dayjs.extend(window.dayjs_plugin_utc);
dayjs.extend(window.dayjs_plugin_relativeTime);
Alpine.data('elapsedTime', (uuid, status, created_at, updated_at) => ({
finished_time: 'calculating...',
started_time: 'calculating...',
init() {
if (timers[uuid]) {
clearInterval(timers[uuid]);
}
if (status === 'in_progress') {
timers[uuid] = setInterval(() => {
this.finished_time = dayjs().diff(dayjs.utc(created_at),
'second') + 's'
}, 1000);
} else {
let seconds = dayjs.utc(updated_at).diff(dayjs.utc(created_at), 'second')
this.finished_time = seconds + 's';
}
},
measure_finished_time() {
return this.finished_time;
},
measure_since_started() {
return dayjs.utc(created_at).fromNow();
}
}))
})
</script>
</div>

View File

@ -84,7 +84,7 @@
wire:click="stop({{ data_get($preview, 'pull_request_id') }})">Remove Preview wire:click="stop({{ data_get($preview, 'pull_request_id') }})">Remove Preview
</x-forms.button> </x-forms.button>
<a <a
href="{{ route('project.application.deployments', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}"> href="{{ route('project.application.deployment.index', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}">
<x-forms.button class="bg-coolgray-500"> <x-forms.button class="bg-coolgray-500">
Get Deployment Logs Get Deployment Logs
</x-forms.button> </x-forms.button>

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