diff --git a/README.md b/README.md index 3a211d4b1..db38eb142 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,12 @@ # Donations Thank you so much! -Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)! +Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)! cccareers logo -appwrite logo ## Github Sponsors ($40+) +American Cloud CryptoJobsList typebot BC Direct diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 02ba017ca..214843aab 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -22,7 +22,6 @@ class Github extends Controller public function manual(Request $request) { try { - ray($request); $return_payloads = collect([]); $x_github_delivery = request()->header('X-GitHub-Delivery'); if (app()->isDownForMaintenance()) { @@ -68,6 +67,10 @@ public function manual(Request $request) if (Str::isMatch('/refs\/heads\/*/', $branch)) { $branch = Str::after($branch, 'refs/heads/'); } + $added_files = data_get($payload, 'commits.*.added'); + $removed_files = data_get($payload, 'commits.*.removed'); + $modified_files = data_get($payload, 'commits.*.modified'); + $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); ray('Manual Webhook GitHub Push Event with branch: ' . $branch); } if ($x_github_event === 'pull_request') { @@ -118,24 +121,41 @@ public function manual(Request $request) } if ($x_github_event === 'push') { if ($application->isDeployable()) { - ray('Deploying ' . $application->name . ' with branch ' . $branch); - $deployment_uuid = new Cuid2(7); - queue_application_deployment( - application: $application, - deployment_uuid: $deployment_uuid, - force_rebuild: false, - is_webhook: true, - ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Deployment queued.', - ]); + $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); + if ($is_watch_path_triggered || is_null($application->watch_paths)) { + ray('Deploying ' . $application->name . ' with branch ' . $branch); + $deployment_uuid = new Cuid2(7); + queue_application_deployment( + application: $application, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + is_webhook: true, + ); + $return_payloads->push([ + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } else { + $paths = str($application->watch_paths)->explode("\n"); + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Changed files do not match watch paths. Ignoring deployment.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + 'details' => [ + 'changed_files' => $changed_files, + 'watch_paths' => $paths, + ], + ]); + } } else { $return_payloads->push([ - 'application' => $application->name, 'status' => 'failed', 'message' => 'Deployments disabled.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, ]); } } @@ -266,6 +286,10 @@ public function normal(Request $request) if (Str::isMatch('/refs\/heads\/*/', $branch)) { $branch = Str::after($branch, 'refs/heads/'); } + $added_files = data_get($payload, 'commits.*.added'); + $removed_files = data_get($payload, 'commits.*.removed'); + $modified_files = data_get($payload, 'commits.*.modified'); + $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); ray('Webhook GitHub Push Event: ' . $id . ' with branch: ' . $branch); } if ($x_github_event === 'pull_request') { @@ -298,32 +322,50 @@ public function normal(Request $request) $isFunctional = $application->destination->server->isFunctional(); if (!$isFunctional) { $return_payloads->push([ - 'application' => $application->name, 'status' => 'failed', 'message' => 'Server is not functional.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, ]); continue; } if ($x_github_event === 'push') { if ($application->isDeployable()) { - ray('Deploying ' . $application->name . ' with branch ' . $branch); - $deployment_uuid = new Cuid2(7); - queue_application_deployment( - application: $application, - deployment_uuid: $deployment_uuid, - force_rebuild: false, - is_webhook: true - ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Deployment queued.', - ]); + $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); + if ($is_watch_path_triggered || is_null($application->watch_paths)) { + ray('Deploying ' . $application->name . ' with branch ' . $branch); + $deployment_uuid = new Cuid2(7); + queue_application_deployment( + application: $application, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + is_webhook: true, + ); + $return_payloads->push([ + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } else { + $paths = str($application->watch_paths)->explode("\n"); + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Changed files do not match watch paths. Ignoring deployment.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + 'details' => [ + 'changed_files' => $changed_files, + 'watch_paths' => $paths, + ], + ]); + } } else { $return_payloads->push([ - 'application' => $application->name, 'status' => 'failed', 'message' => 'Deployments disabled.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, ]); } } diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index 5b2911e88..65ce9910b 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -51,6 +51,10 @@ public function manual(Request $request) ]); return response($return_payloads); } + $added_files = data_get($payload, 'commits.*.added'); + $removed_files = data_get($payload, 'commits.*.removed'); + $modified_files = data_get($payload, 'commits.*.modified'); + $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); ray('Manual Webhook GitLab Push Event with branch: ' . $branch); } if ($x_gitlab_event === 'merge_request') { @@ -113,19 +117,41 @@ public function manual(Request $request) } if ($x_gitlab_event === 'push') { if ($application->isDeployable()) { - ray('Deploying ' . $application->name . ' with branch ' . $branch); - $deployment_uuid = new Cuid2(7); - queue_application_deployment( - application: $application, - deployment_uuid: $deployment_uuid, - force_rebuild: false, - is_webhook: true - ); + $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); + if ($is_watch_path_triggered || is_null($application->watch_paths)) { + ray('Deploying ' . $application->name . ' with branch ' . $branch); + $deployment_uuid = new Cuid2(7); + queue_application_deployment( + application: $application, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + is_webhook: true, + ); + $return_payloads->push([ + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } else { + $paths = str($application->watch_paths)->explode("\n"); + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Changed files do not match watch paths. Ignoring deployment.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + 'details' => [ + 'changed_files' => $changed_files, + 'watch_paths' => $paths, + ], + ]); + } } else { $return_payloads->push([ - 'application' => $application->name, 'status' => 'failed', 'message' => 'Deployments disabled', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, ]); ray('Deployments disabled for ' . $application->name); } diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 0c6b6eecb..51c024af6 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -234,7 +234,7 @@ public function handle(): void $this->build_server = $this->server; $this->original_server = $this->server; } - if ($this->restart_only && $this->application->build_pack !== 'dockerimage') { + if ($this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile') { $this->just_restart(); if ($this->server->isProxyShouldRun()) { dispatch(new ContainerStatusJob($this->server)); @@ -326,17 +326,19 @@ private function deploy_simple_dockerfile() ], ); $this->generate_image_names(); - if (!$this->force_rebuild) { - $this->check_image_locally_or_remotely(); - if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { - $this->create_workdir(); - $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); - $this->generate_compose_file(); - $this->push_to_docker_registry(); - $this->rolling_update(); - return; - } - } + + // Always rebuild dockerfile based container. + // if (!$this->force_rebuild) { + // $this->check_image_locally_or_remotely(); + // if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { + // $this->create_workdir(); + // $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); + // $this->generate_compose_file(); + // $this->push_to_docker_registry(); + // $this->rolling_update(); + // return; + // } + // } $this->generate_compose_file(); $this->generate_build_env_variables(); $this->add_build_env_variables_to_dockerfile(); diff --git a/app/Livewire/Admin/Index.php b/app/Livewire/Admin/Index.php index 60bd6f5ea..b72bc8e35 100644 --- a/app/Livewire/Admin/Index.php +++ b/app/Livewire/Admin/Index.php @@ -8,7 +8,31 @@ class Index extends Component { - public $users = []; + public $active_subscribers = []; + public $inactive_subscribers = []; + public $search = ''; + public function submitSearch() { + if ($this->search !== "") { + $this->inactive_subscribers = User::whereDoesntHave('teams', function ($query) { + $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null); + })->where(function ($query) { + $query->where('name', 'like', "%{$this->search}%") + ->orWhere('email', 'like', "%{$this->search}%"); + })->get()->filter(function ($user) { + return $user->id !== 0; + }); + $this->active_subscribers = User::whereHas('teams', function ($query) { + $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null); + })->where(function ($query) { + $query->where('name', 'like', "%{$this->search}%") + ->orWhere('email', 'like', "%{$this->search}%"); + })->get()->filter(function ($user) { + return $user->id !== 0; + }); + } else { + $this->getSubscribers(); + } + } public function mount() { if (!isCloud()) { @@ -17,7 +41,15 @@ public function mount() if (auth()->user()->id !== 0) { return redirect()->route('dashboard'); } - $this->users = User::whereHas('teams', function ($query) { + $this->getSubscribers(); + } + public function getSubscribers() { + $this->inactive_subscribers = User::whereDoesntHave('teams', function ($query) { + $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null); + })->get()->filter(function ($user) { + return $user->id !== 0; + }); + $this->active_subscribers = User::whereHas('teams', function ($query) { $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null); })->get()->filter(function ($user) { return $user->id !== 0; diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 9485cdd03..c6b8f2e51 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -73,6 +73,7 @@ class General extends Component 'application.settings.is_static' => 'boolean|required', 'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required', 'application.settings.is_build_server_enabled' => 'boolean|required', + 'application.watch_paths' => 'nullable', ]; protected $validationAttributes = [ 'application.name' => 'name', @@ -108,6 +109,7 @@ class General extends Component 'application.settings.is_static' => 'Is static', 'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled', 'application.settings.is_build_server_enabled' => 'Is build server enabled', + 'application.watch_paths' => 'Watch paths', ]; public function mount() { diff --git a/app/Livewire/Project/New/GithubPrivateRepository.php b/app/Livewire/Project/New/GithubPrivateRepository.php index 7d59cfb04..322fd4a4e 100644 --- a/app/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Livewire/Project/New/GithubPrivateRepository.php @@ -7,14 +7,12 @@ use App\Models\Project; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; -use App\Traits\SaveFromRedirect; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Route; use Livewire\Component; class GithubPrivateRepository extends Component { - use SaveFromRedirect; public $current_step = 'github_apps'; public $github_apps; public GithubApp $github_app; diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index b1842547e..2662cff09 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -70,6 +70,10 @@ public function updatedSelectedEnvironment() // } // } + public function updatedSearch() + { + $this->loadServices(); + } public function loadServices(bool $force = false) { try { diff --git a/app/Livewire/Project/Service/ServiceApplicationView.php b/app/Livewire/Project/Service/ServiceApplicationView.php index 0b3a4cef6..0986987f9 100644 --- a/app/Livewire/Project/Service/ServiceApplicationView.php +++ b/app/Livewire/Project/Service/ServiceApplicationView.php @@ -24,6 +24,16 @@ public function render() { return view('livewire.project.service.service-application-view'); } + public function updatedApplicationFqdn() + { + $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); + $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); + $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { + return str($domain)->trim()->lower(); + }); + $this->application->fqdn = $this->application->fqdn->unique()->implode(','); + $this->application->save(); + } public function instantSave() { $this->submit(); @@ -59,7 +69,11 @@ public function submit() $this->validate(); $this->application->save(); updateCompose($this->application); - $this->dispatch('success', 'Service saved.'); + if (str($this->application->fqdn)->contains(',')) { + $this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.'); + } else { + $this->dispatch('success', 'Service saved.'); + } } catch (\Throwable $e) { return handleError($e, $this); } finally { diff --git a/app/Livewire/Security/PrivateKey/Create.php b/app/Livewire/Security/PrivateKey/Create.php index cd1c06568..30449b220 100644 --- a/app/Livewire/Security/PrivateKey/Create.php +++ b/app/Livewire/Security/PrivateKey/Create.php @@ -26,7 +26,7 @@ class Create extends Component 'value' => 'private Key', ]; - public function generateNewKey() + public function generateNewRSAKey() { try { $this->rateLimit(10); @@ -37,6 +37,17 @@ public function generateNewKey() return handleError($e, $this); } } + public function generateNewEDKey() + { + try { + $this->rateLimit(10); + $this->name = generate_random_name(); + $this->description = 'Created by Coolify'; + ['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey('ed25519'); + } catch(\Throwable $e) { + return handleError($e, $this); + } + } public function updated($updateProperty) { if ($updateProperty === 'value') { diff --git a/app/Livewire/Security/PrivateKey/Show.php b/app/Livewire/Security/PrivateKey/Show.php index 0540b2e29..0a292731b 100644 --- a/app/Livewire/Security/PrivateKey/Show.php +++ b/app/Livewire/Security/PrivateKey/Show.php @@ -8,7 +8,7 @@ class Show extends Component { public PrivateKey $private_key; - public $public_key; + public $public_key = "Loading..."; protected $rules = [ 'private_key.name' => 'required|string', 'private_key.description' => 'nullable|string', @@ -25,11 +25,13 @@ public function mount() { 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(); }catch(\Throwable $e) { return handleError($e, $this); } } + public function loadPublicKey() { + $this->public_key = $this->private_key->publicKey(); + } public function delete() { try { diff --git a/app/Livewire/Server/Resources.php b/app/Livewire/Server/Resources.php index f781418d9..1c8a8267e 100644 --- a/app/Livewire/Server/Resources.php +++ b/app/Livewire/Server/Resources.php @@ -42,7 +42,11 @@ public function refreshStatus() { $this->dispatch('success', 'Resource statuses refreshed.'); } public function loadUnmanagedContainers() { - $this->unmanagedContainers = $this->server->loadUnmanagedContainers(); + try { + $this->unmanagedContainers = $this->server->loadUnmanagedContainers(); + } catch (\Throwable $e) { + return handleError($e, $this); + } } public function mount() { $this->unmanagedContainers = collect(); diff --git a/app/Livewire/Subscription/Index.php b/app/Livewire/Subscription/Index.php index c87f7d0b6..b367e6dcc 100644 --- a/app/Livewire/Subscription/Index.php +++ b/app/Livewire/Subscription/Index.php @@ -15,7 +15,10 @@ public function mount() if (!isCloud()) { return redirect(RouteServiceProvider::HOME); } - if (data_get(currentTeam(), 'subscription')) { + if (auth()->user()?->isMember()) { + return redirect()->route('dashboard'); + } + if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) { return redirect()->route('subscription.show'); } $this->settings = InstanceSettings::get(); diff --git a/app/Livewire/Subscription/Show.php b/app/Livewire/Subscription/Show.php index ad677ce53..2ae89806d 100644 --- a/app/Livewire/Subscription/Show.php +++ b/app/Livewire/Subscription/Show.php @@ -11,6 +11,9 @@ public function mount() if (!isCloud()) { return redirect()->route('dashboard'); } + if (auth()->user()?->isMember()) { + return redirect()->route('dashboard'); + } if (!data_get(currentTeam(), 'subscription')) { return redirect()->route('subscription.index'); } diff --git a/app/Models/Application.php b/app/Models/Application.php index 3263d8d72..6a57e76e0 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Collection; use Spatie\Activitylog\Models\Activity; use Illuminate\Support\Str; use RuntimeException; @@ -501,9 +502,9 @@ public function isConfigurationChanged(bool $save = false) { $newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels; if ($this->pull_request_id === 0 || $this->pull_request_id === null) { - $newConfigHash .= json_encode($this->environment_variables()); + $newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); } else { - $newConfigHash .= json_encode($this->environment_variables_preview->all()); + $newConfigHash .= json_encode($this->environment_variables_preview->get('updated_at')); } $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); @@ -905,7 +906,6 @@ public function fqdns(): Attribute : explode(',', $this->fqdn), ); } - protected function buildGitCheckoutCommand($target): string { $command = "git checkout $target"; @@ -914,5 +914,28 @@ protected function buildGitCheckoutCommand($target): string { } return $command; + + public function watchPaths(): Attribute + { + return Attribute::make( + set: function ($value) { + if ($value) { + return trim($value); + } + } + ); + } + public function isWatchPathsTriggered(Collection $modified_files): bool + { + if (is_null($this->watch_paths)) { + return false; + } + $watch_paths = collect(explode("\n", $this->watch_paths)); + $matches = $modified_files->filter(function ($file) use ($watch_paths) { + return $watch_paths->contains(function ($glob) use ($file) { + return fnmatch($glob, $file); + }); + }); + return $matches->count() > 0; } } diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 611543b2d..32277769e 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -25,19 +25,18 @@ protected static function booted() static::created(function (EnvironmentVariable $environment_variable) { if ($environment_variable->application_id && !$environment_variable->is_preview) { $found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first(); - $application = Application::find($environment_variable->application_id); - if ($application->build_pack === 'dockerfile') { - return; - } if (!$found) { - ModelsEnvironmentVariable::create([ - 'key' => $environment_variable->key, - 'value' => $environment_variable->value, - 'is_build_time' => $environment_variable->is_build_time, - 'is_multiline' => $environment_variable->is_multiline, - 'application_id' => $environment_variable->application_id, - 'is_preview' => true - ]); + $application = Application::find($environment_variable->application_id); + if ($application->build_pack !== 'dockerfile') { + ModelsEnvironmentVariable::create([ + 'key' => $environment_variable->key, + 'value' => $environment_variable->value, + 'is_build_time' => $environment_variable->is_build_time, + 'is_multiline' => $environment_variable->is_multiline, + 'application_id' => $environment_variable->application_id, + 'is_preview' => true + ]); + } } } $environment_variable->update([ diff --git a/app/Models/Server.php b/app/Models/Server.php index 08235a26d..cbe895936 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -550,21 +550,21 @@ public function startUnmanaged($id) } public function loadUnmanagedContainers() { - if ($this->isFunctional()) { - $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this); - $containers = format_docker_command_output_to_json($containers); - $containers = $containers->map(function ($container) { - $labels = data_get($container, 'Labels'); - if (!str($labels)->contains("coolify.managed")) { - return $container; - } - return null; - }); - $containers = $containers->filter(); - return collect($containers); - } else { - return collect([]); - } + if ($this->isFunctional()) { + $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this); + $containers = format_docker_command_output_to_json($containers); + $containers = $containers->map(function ($container) { + $labels = data_get($container, 'Labels'); + if (!str($labels)->contains("coolify.managed")) { + return $container; + } + return null; + }); + $containers = $containers->filter(); + return collect($containers); + } else { + return collect([]); + } } public function hasDefinedResources() { diff --git a/app/Models/Team.php b/app/Models/Team.php index a3dd4e473..29e434a5d 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -21,9 +21,11 @@ class Team extends Model implements SendsDiscord, SendsEmail protected static function booted() { - // static::saved(function () { - // refreshSession(); - // }); + static::saving(function ($team) { + if (auth()->user()?->isMember()) { + throw new \Exception('You are not allowed to update this team.'); + } + }); } public function routeNotificationForDiscord() diff --git a/app/Models/User.php b/app/Models/User.php index e2ecae56a..0fa8ead2f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -127,6 +127,10 @@ public function isOwner() { return $this->role() === 'owner'; } + public function isMember() + { + return $this->role() === 'member'; + } public function isAdminFromSession() { if (auth()->user()->id === 0) { diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index bf3f15514..8d810da0f 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -6,6 +6,7 @@ use App\Models\ApplicationDeploymentQueue; use App\Models\Server; use App\Models\StandaloneDocker; +use Illuminate\Support\Collection; use Spatie\Url\Url; function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 023a65107..458dbae85 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -39,6 +39,7 @@ use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Token\Builder; +use phpseclib3\Crypt\EC; use Poliander\Cron\CronExpression; use Visus\Cuid2\Cuid2; use phpseclib3\Crypt\RSA; @@ -165,13 +166,22 @@ function generate_random_name(?string $cuid = null): string } return Str::kebab("{$generator->getName()}-$cuid"); } -function generateSSHKey() +function generateSSHKey(string $type = 'rsa') { - $key = RSA::createKey(); - return [ - 'private' => $key->toString('PKCS1'), - 'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key']) - ]; + if ($type === 'rsa') { + $key = RSA::createKey(); + return [ + 'private' => $key->toString('PKCS1'), + 'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key']) + ]; + } else if ($type === 'ed25519') { + $key = EC::createKey('Ed25519'); + return [ + 'private' => $key->toString('OpenSSH'), + 'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key']) + ]; + } + throw new Exception('Invalid key type'); } function formatPrivateKey(string $privateKey) { @@ -282,7 +292,7 @@ function base_url(bool $withPort = true): string function isSubscribed() { - return auth()->user()->currentTeam()->subscription()->exists() || auth()->user()->isInstanceAdmin(); + return isSubscriptionActive() || auth()->user()->isInstanceAdmin(); } function isDev(): bool { diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php index 928bbbcaf..5158c4e7e 100644 --- a/bootstrap/helpers/subscriptions.php +++ b/bootstrap/helpers/subscriptions.php @@ -66,7 +66,7 @@ function isSubscriptionActive() // return $subscription->paddle_status === 'active'; // } if (isStripe()) { - return $subscription->stripe_invoice_paid === true && $subscription->stripe_cancel_at_period_end === false; + return $subscription->stripe_invoice_paid === true; } return false; } diff --git a/config/sentry.php b/config/sentry.php index 89345d082..a3fbc0a3a 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.248', + 'release' => '4.0.0-beta.251', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index 642ee812d..d88e65a1b 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ longText('watch_paths')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('watch_paths'); + }); + } +}; diff --git a/resources/css/app.css b/resources/css/app.css index b7965ebbc..371efd8ed 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -13,7 +13,7 @@ body { .input, .select { - @apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-300 dark:ring-coolgray-300; + @apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300; } /* Readonly */ @@ -41,7 +41,7 @@ option { } .button { - @apply flex items-center justify-center gap-2 px-3 py-1 text-sm text-white normal-case rounded cursor-pointer hover:bg-black/80 bg-coolgray-200 hover:bg-coolgray-500 hover:text-white disabled:bg-coolgray-100/10 disabled:cursor-not-allowed min-w-fit focus:outline-1 dark:disabled:text-neutral-600; + @apply flex items-center justify-center gap-2 px-2 py-1 text-sm text-black normal-case border rounded cursor-pointer bg-neutral-200/50 border-neutral-300 hover:bg-neutral-300 dark:bg-coolgray-200 dark:text-white dark:hover:text-white dark:hover:bg-coolgray-500 dark:border-black hover:text-black disabled:bg-coolgray-100/10 disabled:cursor-not-allowed min-w-fit focus:outline-1 dark:disabled:text-neutral-600; } button[isError]:not(:disabled) { @@ -78,7 +78,7 @@ label { } table { - @apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300; + @apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300 ; } thead { @@ -90,7 +90,7 @@ tbody { } tr { - @apply text-neutral-400; + @apply text-black dark:text-neutral-400 dark:hover:bg-black hover:bg-neutral-200; } tr th { @@ -203,6 +203,9 @@ .box-boarding { .box-without-bg { @apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] border border-neutral-200 dark:border-black; } +.box-without-bg-without-border { + @apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] ; +} .on-box { @apply rounded hover:bg-neutral-300 dark:hover:bg-coolgray-500/20; diff --git a/resources/js/app.js b/resources/js/app.js index 47313863b..befec919e 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,6 +1,6 @@ -import { createApp } from "vue"; -import MagicBar from "./components/MagicBar.vue"; +// import { createApp } from "vue"; +// import MagicBar from "./components/MagicBar.vue"; -const app = createApp({}); -app.component("magic-bar", MagicBar); -app.mount("#vue"); +// const app = createApp({}); +// app.component("magic-bar", MagicBar); +// app.mount("#vue"); diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 82ea5062b..2c61581c6 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -4,51 +4,31 @@ Coolify -
+
@csrf @env('local') - + - + {{ __('auth.forgot_password') }}? @else - - + + {{ __('auth.forgot_password') }}? @endenv {{ __('auth.login') }} - @if ($is_registration_enabled) - - {{ __('auth.register_now') }} - - @endif - @if ($enabled_oauth_providers->isNotEmpty()) -
- -
- or -
-
- @endif - @foreach ($enabled_oauth_providers as $provider_setting) - - {{ __("auth.login.$provider_setting->provider") }} - - @endforeach + @if (!$is_registration_enabled)
{{ __('auth.registration_disabled') }}
@endif @@ -70,6 +50,27 @@ class="w-full bg-white shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base ">
@endif + @if ($is_registration_enabled) + + {{ __('auth.register_now') }} + + @endif + @if ($enabled_oauth_providers->isNotEmpty()) +
+ +
+ or +
+
+ @endif + @foreach ($enabled_oauth_providers as $provider_setting) + + {{ __("auth.login.$provider_setting->provider") }} + + @endforeach
diff --git a/resources/views/auth/two-factor-challenge.blade.php b/resources/views/auth/two-factor-challenge.blade.php index 33094920c..6761992a9 100644 --- a/resources/views/auth/two-factor-challenge.blade.php +++ b/resources/views/auth/two-factor-challenge.blade.php @@ -1,42 +1,40 @@ -
-
-
-
Coolify
- {{-- --}} -
-
-
- @csrf -
- - {{--
Use - Recovery Code -
--}} -
-
- - {{--
Use - One-Time Code -
--}} -
- {{ __('auth.login') }} -
- @if ($errors->any()) -
- @foreach ($errors->all() as $error) -

{{ $error }}

- @endforeach -
- @endif - @if (session('status')) -
- {{ session('status') }} -
- @endif +
+
+ + Coolify + +
+
+
+ @csrf +
+ +
Enter + Recovery Code +
+
+
+ +
+ {{ __('auth.login') }} +
+ @if ($errors->any()) +
+ @foreach ($errors->all() as $error) +

{{ $error }}

+ @endforeach +
+ @endif + @if (session('status')) +
+ {{ session('status') }} +
+ @endif +
-
+ diff --git a/resources/views/components/forms/input.blade.php b/resources/views/components/forms/input.blade.php index f6b255a9f..2c6c1ef49 100644 --- a/resources/views/components/forms/input.blade.php +++ b/resources/views/components/forms/input.blade.php @@ -3,7 +3,7 @@ 'w-full' => !$isMultiline, ])> @if ($label) -