diff --git a/README.md b/README.md index e45b60b83..dc55c99da 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ ) [![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new) -[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dopen&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=open) -[![Rewarded Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dcompleted&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=completed) # About the Project @@ -49,6 +47,7 @@ Special thanks to our biggest sponsors! advin logo trieve logo blacksmith logo +latitude logo ## Github Sponsors ($40+) SerpAPI diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index 7949b2457..4a276cfc4 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Actions\Server\StopSentinel; use App\Enums\ApplicationDeploymentStatus; use App\Jobs\CleanupHelperContainersJob; use App\Models\ApplicationDeploymentQueue; @@ -23,6 +24,16 @@ class Init extends Command { $this->alive(); get_public_ips(); + if (version_compare('4.0.0-beta.312', config('version'), '<=')) { + $servers = Server::all(); + foreach ($servers as $server) { + $server->settings->update(['is_metrics_enabled' => false]); + if ($server->isFunctional()) { + StopSentinel::dispatch($server); + } + } + } + $full_cleanup = $this->option('full-cleanup'); $cleanup_deployments = $this->option('cleanup-deployments'); diff --git a/app/Events/ServiceStatusChanged.php b/app/Events/ServiceStatusChanged.php index dc965d0a2..a86a8b02d 100644 --- a/app/Events/ServiceStatusChanged.php +++ b/app/Events/ServiceStatusChanged.php @@ -27,7 +27,7 @@ class ServiceStatusChanged implements ShouldBroadcast public function broadcastOn(): ?array { - if ($this->userId) { + if (! is_null($this->userId)) { return [ new PrivateChannel("user.{$this->userId}"), ]; diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 9607cffec..6b69350fe 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -65,7 +65,7 @@ class Handler extends ExceptionHandler if ($e instanceof RuntimeException) { return; } - $this->settings = InstanceSettings::get(); + $this->settings = \App\Models\InstanceSettings::get(); if ($this->settings->do_not_track) { return; } diff --git a/app/Http/Controllers/Api/OtherController.php b/app/Http/Controllers/Api/OtherController.php index 96dded3ce..1e48ffdbe 100644 --- a/app/Http/Controllers/Api/OtherController.php +++ b/app/Http/Controllers/Api/OtherController.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; -use App\Models\InstanceSettings; use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; use OpenApi\Attributes as OA; @@ -85,7 +84,7 @@ class OtherController extends Controller if ($teamId !== '0') { return response()->json(['message' => 'You are not allowed to enable the API.'], 403); } - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); $settings->update(['is_api_enabled' => true]); return response()->json(['message' => 'API enabled.'], 200); @@ -136,7 +135,7 @@ class OtherController extends Controller if ($teamId !== '0') { return response()->json(['message' => 'You are not allowed to disable the API.'], 403); } - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); $settings->update(['is_api_enabled' => false]); return response()->json(['message' => 'API disabled.'], 200); diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index 247a2519f..da9c3b2d8 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -4,7 +4,6 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Models\Application; -use App\Models\InstanceSettings; use App\Models\Project; use App\Models\Server as ModelsServer; use Illuminate\Http\Request; @@ -301,7 +300,7 @@ class ServersController extends Controller $projects = Project::where('team_id', $teamId)->get(); $domains = collect(); $applications = $projects->pluck('applications')->flatten(); - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); if ($applications->count() > 0) { foreach ($applications as $application) { $ip = $application->destination->server->ip; diff --git a/app/Http/Middleware/ApiAllowed.php b/app/Http/Middleware/ApiAllowed.php index dc0a433e2..648720ba4 100644 --- a/app/Http/Middleware/ApiAllowed.php +++ b/app/Http/Middleware/ApiAllowed.php @@ -2,7 +2,6 @@ namespace App\Http\Middleware; -use App\Models\InstanceSettings; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; @@ -15,7 +14,7 @@ class ApiAllowed if (isCloud()) { return $next($request); } - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); if ($settings->is_api_enabled === false) { return response()->json(['success' => true, 'message' => 'API is disabled.'], 403); } diff --git a/app/Jobs/PullCoolifyImageJob.php b/app/Jobs/PullCoolifyImageJob.php index 2bcbfc4df..253b0b9f0 100644 --- a/app/Jobs/PullCoolifyImageJob.php +++ b/app/Jobs/PullCoolifyImageJob.php @@ -2,7 +2,6 @@ namespace App\Jobs; -use App\Models\InstanceSettings; use App\Models\Server; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; @@ -36,7 +35,7 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue $latest_version = get_latest_version_of_coolify(); instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false); - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); $current_version = config('version'); if (! $settings->is_auto_update_enabled) { return; diff --git a/app/Livewire/Help.php b/app/Livewire/Help.php index 2fbd2bc7e..0289f5f36 100644 --- a/app/Livewire/Help.php +++ b/app/Livewire/Help.php @@ -2,7 +2,6 @@ namespace App\Livewire; -use App\Models\InstanceSettings; use DanHarrin\LivewireRateLimiting\WithRateLimiting; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\Http; @@ -48,7 +47,7 @@ class Help extends Component ] ); $mail->subject("[HELP]: {$this->subject}"); - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); $type = set_transanctional_email_settings($settings); if (! $type) { $url = 'https://app.coolify.io/api/feedback'; diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php index 91c108edc..2960ed226 100644 --- a/app/Livewire/Notifications/Email.php +++ b/app/Livewire/Notifications/Email.php @@ -2,7 +2,6 @@ namespace App\Livewire\Notifications; -use App\Models\InstanceSettings; use App\Models\Team; use App\Notifications\Test; use Livewire\Component; @@ -173,7 +172,7 @@ class Email extends Component public function copyFromInstanceSettings() { - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); if ($settings->smtp_enabled) { $team = currentTeam(); $team->update([ diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 48be89714..91828d42c 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -350,7 +350,6 @@ class General extends Component $this->checkFqdns(); $this->application->save(); - if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') { $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n"); $this->application->custom_labels = base64_encode($this->customLabels); @@ -364,6 +363,7 @@ class General extends Component } } $this->validate(); + if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) { $this->resetDefaultLabels(); } @@ -390,6 +390,7 @@ class General extends Component } if ($this->application->build_pack === 'dockercompose') { $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); + foreach ($this->parsedServiceDomains as $serviceName => $service) { $domain = data_get($service, 'domain'); if ($domain) { @@ -399,6 +400,9 @@ class General extends Component check_domain_usage(resource: $this->application); } } + if ($this->application->isDirty('docker_compose_domains')) { + $this->resetDefaultLabels(); + } } $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); diff --git a/app/Livewire/Project/Shared/Tags.php b/app/Livewire/Project/Shared/Tags.php index 85d5c21dc..dca6180ff 100644 --- a/app/Livewire/Project/Shared/Tags.php +++ b/app/Livewire/Project/Shared/Tags.php @@ -3,32 +3,63 @@ namespace App\Livewire\Project\Shared; use App\Models\Tag; +use Livewire\Attributes\Validate; use Livewire\Component; +// Refactored ✅ class Tags extends Component { public $resource = null; - public ?string $new_tag = null; + #[Validate('required|string|min:2')] + public string $newTags; public $tags = []; - protected $listeners = [ - 'refresh' => '$refresh', - ]; - - protected $rules = [ - 'resource.tags.*.name' => 'required|string|min:2', - 'new_tag' => 'required|string|min:2', - ]; - - protected $validationAttributes = [ - 'new_tag' => 'tag', - ]; + public $filteredTags = []; public function mount() + { + $this->loadTags(); + } + + public function loadTags() { $this->tags = Tag::ownedByCurrentTeam()->get(); + $this->filteredTags = $this->tags->filter(function ($tag) { + return ! $this->resource->tags->contains($tag); + }); + } + + public function submit() + { + try { + $this->validate(); + $tags = str($this->newTags)->trim()->explode(' '); + foreach ($tags as $tag) { + if (strlen($tag) < 2) { + $this->dispatch('error', 'Invalid tag.', "Tag $tag is invalid. Min length is 2."); + + continue; + } + if ($this->resource->tags()->where('name', $tag)->exists()) { + $this->dispatch('error', 'Duplicate tags.', "Tag $tag already added."); + + continue; + } + $found = Tag::ownedByCurrentTeam()->where(['name' => $tag])->exists(); + if (! $found) { + $found = Tag::create([ + 'name' => $tag, + 'team_id' => currentTeam()->id, + ]); + } + $this->resource->tags()->attach($found->id); + } + $this->refresh(); + } catch (\Exception $e) { + return handleError($e, $this); + } } public function addTag(string $id, string $name) @@ -39,8 +70,9 @@ class Tags extends Component return; } - $this->resource->tags()->syncWithoutDetaching($id); + $this->resource->tags()->attach($id); $this->refresh(); + $this->dispatch('success', 'Tag added.'); } catch (\Exception $e) { return handleError($e, $this); } @@ -50,12 +82,12 @@ class Tags extends Component { try { $this->resource->tags()->detach($id); - - $found_more_tags = Tag::where(['id' => $id, 'team_id' => currentTeam()->id])->first(); - if ($found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0) { + $found_more_tags = Tag::ownedByCurrentTeam()->find($id); + if ($found_more_tags && $found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0) { $found_more_tags->delete(); } $this->refresh(); + $this->dispatch('success', 'Tag deleted.'); } catch (\Exception $e) { return handleError($e, $this); } @@ -63,41 +95,8 @@ class Tags extends Component public function refresh() { - $this->resource->load(['tags']); - $this->tags = Tag::ownedByCurrentTeam()->get(); - $this->new_tag = null; - } - - public function submit() - { - try { - $this->validate([ - 'new_tag' => 'required|string|min:2', - ]); - $tags = str($this->new_tag)->trim()->explode(' '); - foreach ($tags as $tag) { - if ($this->resource->tags()->where('name', $tag)->exists()) { - $this->dispatch('error', 'Duplicate tags.', "Tag $tag already added."); - - continue; - } - $found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first(); - if (! $found) { - $found = Tag::create([ - 'name' => $tag, - 'team_id' => currentTeam()->id, - ]); - } - $this->resource->tags()->syncWithoutDetaching($found->id); - } - $this->refresh(); - } catch (\Exception $e) { - return handleError($e, $this); - } - } - - public function render() - { - return view('livewire.project.shared.tags'); + $this->resource->refresh(); // Remove this when legacy_model_binding is false + $this->loadTags(); + $this->reset('newTags'); } } diff --git a/app/Livewire/Project/Shared/Webhooks.php b/app/Livewire/Project/Shared/Webhooks.php index e96bd888e..aab1fdc47 100644 --- a/app/Livewire/Project/Shared/Webhooks.php +++ b/app/Livewire/Project/Shared/Webhooks.php @@ -4,49 +4,61 @@ namespace App\Livewire\Project\Shared; use Livewire\Component; +// Refactored ✅ class Webhooks extends Component { public $resource; - public ?string $deploywebhook = null; + public ?string $deploywebhook; - public ?string $githubManualWebhook = null; + public ?string $githubManualWebhook; - public ?string $gitlabManualWebhook = null; + public ?string $gitlabManualWebhook; - public ?string $bitbucketManualWebhook = null; + public ?string $bitbucketManualWebhook; - public ?string $giteaManualWebhook = null; + public ?string $giteaManualWebhook; - protected $rules = [ - 'resource.manual_webhook_secret_github' => 'nullable|string', - 'resource.manual_webhook_secret_gitlab' => 'nullable|string', - 'resource.manual_webhook_secret_bitbucket' => 'nullable|string', - 'resource.manual_webhook_secret_gitea' => 'nullable|string', - ]; + public ?string $githubManualWebhookSecret = null; - public function saveSecret() + public ?string $gitlabManualWebhookSecret = null; + + public ?string $bitbucketManualWebhookSecret = null; + + public ?string $giteaManualWebhookSecret = null; + + public function mount() + { + // ray()->clearAll(); + // ray()->showQueries(); + $this->deploywebhook = generateDeployWebhook($this->resource); + + $this->githubManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_github'); + $this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github'); + + $this->gitlabManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_gitlab'); + $this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab'); + + $this->bitbucketManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_bitbucket'); + $this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket'); + + $this->giteaManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_gitea'); + $this->giteaManualWebhook = generateGitManualWebhook($this->resource, 'gitea'); + } + + public function submit() { try { - $this->validate(); - $this->resource->save(); + $this->authorize('update', $this->resource); + $this->resource->update([ + 'manual_webhook_secret_github' => $this->githubManualWebhookSecret, + 'manual_webhook_secret_gitlab' => $this->gitlabManualWebhookSecret, + 'manual_webhook_secret_bitbucket' => $this->bitbucketManualWebhookSecret, + 'manual_webhook_secret_gitea' => $this->giteaManualWebhookSecret, + ]); $this->dispatch('success', 'Secret Saved.'); } catch (\Exception $e) { return handleError($e, $this); } } - - public function mount() - { - $this->deploywebhook = generateDeployWebhook($this->resource); - $this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github'); - $this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab'); - $this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket'); - $this->giteaManualWebhook = generateGitManualWebhook($this->resource, 'gitea'); - } - - public function render() - { - return view('livewire.project.shared.webhooks'); - } } diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php index f6f918933..e71d2de00 100644 --- a/app/Livewire/Settings/Index.php +++ b/app/Livewire/Settings/Index.php @@ -18,7 +18,7 @@ class Index extends Component public function mount() { if (isInstanceAdmin()) { - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); $database = StandalonePostgresql::whereName('coolify-db')->first(); $s3s = S3Storage::whereTeamId(0)->get() ?? []; if ($database) { diff --git a/app/Livewire/Settings/License.php b/app/Livewire/Settings/License.php index 212bc95be..f9402fd7b 100644 --- a/app/Livewire/Settings/License.php +++ b/app/Livewire/Settings/License.php @@ -29,7 +29,7 @@ class License extends Component abort(404); } $this->instance_id = config('app.id'); - $this->settings = InstanceSettings::get(); + $this->settings = \App\Models\InstanceSettings::get(); } public function render() diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index ee28f8847..75d7fd04a 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -4,7 +4,6 @@ namespace App\Livewire\Source\Github; use App\Jobs\GithubAppPermissionJob; use App\Models\GithubApp; -use App\Models\InstanceSettings; use Illuminate\Support\Facades\Http; use Livewire\Component; @@ -100,7 +99,7 @@ class Change extends Component return redirect()->route('source.all'); } $this->applications = $this->github_app->applications; - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret'); $this->name = str($this->github_app->name)->kebab(); diff --git a/app/Livewire/Subscription/Index.php b/app/Livewire/Subscription/Index.php index c072352fe..c278bf58e 100644 --- a/app/Livewire/Subscription/Index.php +++ b/app/Livewire/Subscription/Index.php @@ -23,7 +23,7 @@ class Index extends Component if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) { return redirect()->route('subscription.show'); } - $this->settings = InstanceSettings::get(); + $this->settings = \App\Models\InstanceSettings::get(); $this->alreadySubscribed = currentTeam()->subscription()->exists(); } diff --git a/app/Models/Server.php b/app/Models/Server.php index 2efc9907b..e164f2e27 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -318,7 +318,7 @@ respond 404 public function setupDynamicProxyConfiguration() { - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); $dynamic_config_path = $this->proxyPath().'/dynamic'; if ($this->proxyType() === 'TRAEFIK_V2') { $file = "$dynamic_config_path/coolify.yaml"; diff --git a/app/Notifications/Channels/TransactionalEmailChannel.php b/app/Notifications/Channels/TransactionalEmailChannel.php index 3d7b7c8d0..549fc6cd3 100644 --- a/app/Notifications/Channels/TransactionalEmailChannel.php +++ b/app/Notifications/Channels/TransactionalEmailChannel.php @@ -2,7 +2,6 @@ namespace App\Notifications\Channels; -use App\Models\InstanceSettings; use App\Models\User; use Exception; use Illuminate\Mail\Message; @@ -14,7 +13,7 @@ class TransactionalEmailChannel { public function send(User $notifiable, Notification $notification): void { - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) { Log::info('SMTP/Resend not enabled'); diff --git a/app/Notifications/TransactionalEmails/ResetPassword.php b/app/Notifications/TransactionalEmails/ResetPassword.php index 45243c4d5..8988a4342 100644 --- a/app/Notifications/TransactionalEmails/ResetPassword.php +++ b/app/Notifications/TransactionalEmails/ResetPassword.php @@ -18,7 +18,7 @@ class ResetPassword extends Notification public function __construct($token) { - $this->settings = InstanceSettings::get(); + $this->settings = \App\Models\InstanceSettings::get(); $this->token = $token; } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 6822dec13..cd90918ad 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,8 +2,10 @@ namespace App\Providers; +use App\Models\InstanceSettings; use App\Models\PersonalAccessToken; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider; use Laravel\Sanctum\Sanctum; @@ -14,6 +16,7 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); + Http::macro('github', function (string $api_url, ?string $github_access_token = null) { if ($github_access_token) { return Http::withHeaders([ @@ -27,5 +30,9 @@ class AppServiceProvider extends ServiceProvider ])->baseUrl($api_url); } }); + // if (! env('CI')) { + // View::share('instanceSettings', InstanceSettings::get()); + // } + } } diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index cd6ec7705..9b0a81026 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -6,7 +6,6 @@ use App\Actions\Fortify\CreateNewUser; use App\Actions\Fortify\ResetUserPassword; use App\Actions\Fortify\UpdateUserPassword; use App\Actions\Fortify\UpdateUserProfileInformation; -use App\Models\InstanceSettings; use App\Models\OauthSetting; use App\Models\User; use Illuminate\Cache\RateLimiting\Limit; @@ -45,7 +44,7 @@ class FortifyServiceProvider extends ServiceProvider { Fortify::createUsersUsing(CreateNewUser::class); Fortify::registerView(function () { - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); if (! $settings->is_registration_enabled) { return redirect()->route('login'); } @@ -57,7 +56,7 @@ class FortifyServiceProvider extends ServiceProvider }); Fortify::loginView(function () { - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); $enabled_oauth_providers = OauthSetting::where('enabled', true)->get(); $users = User::count(); if ($users == 0) { diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index e0272fa4c..24e596a0f 100644 --- a/bootstrap/helpers/constants.php +++ b/bootstrap/helpers/constants.php @@ -40,6 +40,7 @@ const SUPPORTED_OS = [ 'ubuntu debian raspbian', 'centos fedora rhel ol rocky amzn almalinux', 'sles opensuse-leap opensuse-tumbleweed', + 'arch', ]; const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment']; diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 1efe99436..4480c65f4 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -244,13 +244,13 @@ function generate_application_name(string $git_repository, string $git_branch, ? function is_transactional_emails_active(): bool { - return isEmailEnabled(InstanceSettings::get()); + return isEmailEnabled(\App\Models\InstanceSettings::get()); } function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string { if (! $settings) { - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); } config()->set('mail.from.address', data_get($settings, 'smtp_from_address')); config()->set('mail.from.name', data_get($settings, 'smtp_from_name')); @@ -284,7 +284,7 @@ function base_ip(): string if (isDev()) { return 'localhost'; } - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); if ($settings->public_ipv4) { return "$settings->public_ipv4"; } @@ -312,7 +312,7 @@ function getFqdnWithoutPort(string $fqdn) */ function base_url(bool $withPort = true): string { - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); if ($settings->fqdn) { return $settings->fqdn; } @@ -379,7 +379,7 @@ function send_internal_notification(string $message): void } function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void { - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); $type = set_transanctional_email_settings($settings); if (! $type) { throw new Exception('No email settings found.'); @@ -774,6 +774,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $allServices = get_service_templates(); $topLevelVolumes = collect(data_get($yaml, 'volumes', [])); $topLevelNetworks = collect(data_get($yaml, 'networks', [])); + $topLevelConfigs = collect(data_get($yaml, 'configs', [])); + $topLevelSecrets = collect(data_get($yaml, 'secrets', [])); $services = data_get($yaml, 'services'); $generatedServiceFQDNS = collect([]); @@ -1402,6 +1404,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'services' => $services->toArray(), 'volumes' => $topLevelVolumes->toArray(), 'networks' => $topLevelNetworks->toArray(), + 'configs' => $topLevelConfigs->toArray(), + 'secrets' => $topLevelSecrets->toArray(), ]; $yaml = data_forget($yaml, 'services.*.volumes.*.content'); $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); @@ -1441,6 +1445,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } $topLevelNetworks = collect(data_get($yaml, 'networks', [])); + $topLevelConfigs = collect(data_get($yaml, 'configs', [])); + $topLevelSecrets = collect(data_get($yaml, 'secrets', [])); $services = data_get($yaml, 'services'); $generatedServiceFQDNS = collect([]); @@ -2027,14 +2033,20 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal domains: $fqdns, serviceLabels: $serviceLabels, generate_unique_uuid: $resource->build_pack === 'dockercompose', - image: data_get($service, 'image') + image: data_get($service, 'image'), + is_force_https_enabled: $resource->isForceHttpsEnabled(), + is_gzip_enabled: $resource->isGzipEnabled(), + is_stripprefix_enabled: $resource->isStripprefixEnabled(), )); $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( network: $resource->destination->network, uuid: $resource->uuid, domains: $fqdns, serviceLabels: $serviceLabels, - image: data_get($service, 'image') + image: data_get($service, 'image'), + is_force_https_enabled: $resource->isForceHttpsEnabled(), + is_gzip_enabled: $resource->isGzipEnabled(), + is_stripprefix_enabled: $resource->isStripprefixEnabled(), )); } } @@ -2080,6 +2092,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'services' => $services->toArray(), 'volumes' => $topLevelVolumes->toArray(), 'networks' => $topLevelNetworks->toArray(), + 'configs' => $topLevelConfigs->toArray(), + 'secrets' => $topLevelSecrets->toArray(), ]; if ($isSameDockerComposeFile) { $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); @@ -2244,7 +2258,7 @@ function validate_dns_entry(string $fqdn, Server $server) if (str($host)->contains('sslip.io')) { return true; } - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); $is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled'); if (! $is_dns_validation_enabled) { return true; @@ -2364,7 +2378,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = if ($domainFound) { return true; } - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); if (data_get($settings, 'fqdn')) { $domain = data_get($settings, 'fqdn'); if (str($domain)->endsWith('/')) { @@ -2381,7 +2395,6 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null if ($resource) { if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose') { $domains = data_get(json_decode($resource->docker_compose_domains, true), '*.domain'); - ray($domains); $domains = collect($domains); } else { $domains = collect($resource->fqdns); @@ -2437,7 +2450,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null } } if ($resource) { - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); if (data_get($settings, 'fqdn')) { $domain = data_get($settings, 'fqdn'); if (str($domain)->endsWith('/')) { @@ -2512,7 +2525,7 @@ function get_public_ips() { try { echo "Refreshing public ips!\n"; - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); [$first, $second] = Process::concurrently(function (Pool $pool) { $pool->path(__DIR__)->command('curl -4s https://ifconfig.io'); $pool->path(__DIR__)->command('curl -6s https://ifconfig.io'); diff --git a/composer.json b/composer.json index 87ea72472..a3189d341 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "poliander/cron": "^3.0", "purplepixie/phpdns": "^2.1", "pusher/pusher-php-server": "^7.2", + "resend/resend-laravel": "^0.13.0", "sentry/sentry-laravel": "^4.6", "socialiteproviders/microsoft-azure": "^5.1", "spatie/laravel-activitylog": "^4.7.3", @@ -106,4 +107,4 @@ }, "minimum-stability": "stable", "prefer-stable": true -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 38ceda00f..367dda58d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c7c9cc002a9765c2395717c69ba8bfc6", + "content-hash": "cb17445966de6094aef5a92ee59d1d77", "packages": [ { "name": "amphp/amp", @@ -7129,6 +7129,132 @@ ], "time": "2024-07-01T14:24:45+00:00" }, + { + "name": "resend/resend-laravel", + "version": "v0.13.0", + "source": { + "type": "git", + "url": "https://github.com/resend/resend-laravel.git", + "reference": "23aed22df0d0b23c2952da2aaed6a8b88d301a8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/resend/resend-laravel/zipball/23aed22df0d0b23c2952da2aaed6a8b88d301a8a", + "reference": "23aed22df0d0b23c2952da2aaed6a8b88d301a8a", + "shasum": "" + }, + "require": { + "illuminate/http": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0", + "php": "^8.1", + "resend/resend-php": "^0.12.0", + "symfony/mailer": "^6.2|^7.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.14", + "mockery/mockery": "^1.5", + "orchestra/testbench": "^8.17|^9.0", + "pestphp/pest": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + }, + "laravel": { + "providers": [ + "Resend\\Laravel\\ResendServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Resend\\Laravel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Resend and contributors", + "homepage": "https://github.com/resend/resend-laravel/contributors" + } + ], + "description": "Resend for Laravel", + "homepage": "https://resend.com/", + "keywords": [ + "api", + "client", + "laravel", + "php", + "resend", + "sdk" + ], + "support": { + "issues": "https://github.com/resend/resend-laravel/issues", + "source": "https://github.com/resend/resend-laravel/tree/v0.13.0" + }, + "time": "2024-07-08T18:51:42+00:00" + }, + { + "name": "resend/resend-php", + "version": "v0.12.0", + "source": { + "type": "git", + "url": "https://github.com/resend/resend-php.git", + "reference": "37fb79bb8160ce2de521bf37484ba59e89236521" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/resend/resend-php/zipball/37fb79bb8160ce2de521bf37484ba59e89236521", + "reference": "37fb79bb8160ce2de521bf37484ba59e89236521", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.5", + "php": "^8.1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.13", + "mockery/mockery": "^1.6", + "pestphp/pest": "^2.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resend.php" + ], + "psr-4": { + "Resend\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Resend and contributors", + "homepage": "https://github.com/resend/resend-php/contributors" + } + ], + "description": "Resend PHP library.", + "homepage": "https://resend.com/", + "keywords": [ + "api", + "client", + "php", + "resend", + "sdk" + ], + "support": { + "issues": "https://github.com/resend/resend-php/issues", + "source": "https://github.com/resend/resend-php/tree/v0.12.0" + }, + "time": "2024-03-04T03:16:28+00:00" + }, { "name": "revolt/event-loop", "version": "v1.0.6", diff --git a/config/sentry.php b/config/sentry.php index df4aa6283..a99743717 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // 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.309', + 'release' => '4.0.0-beta.313', // 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 3f8d8e2ff..26791fd27 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ output(); $ipv4 = trim($ipv4); $ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP); - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); if (is_null($settings->public_ipv4) && $ipv4) { $settings->update(['public_ipv4' => $ipv4]); } $ipv6 = Process::run('curl -6s https://ifconfig.io')->output(); $ipv6 = trim($ipv6); $ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP); - $settings = InstanceSettings::get(); + $settings = \App\Models\InstanceSettings::get(); if (is_null($settings->public_ipv6) && $ipv6) { $settings->update(['public_ipv6' => $ipv6]); } diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 4ee3fade2..f46124062 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -17,6 +17,7 @@ ARG TARGETPLATFORM # https://github.com/cloudflare/cloudflared/releases ARG CLOUDFLARED_VERSION=2024.4.1 ARG POSTGRES_VERSION=15 +ARG CI=true WORKDIR /var/www/html diff --git a/other/logos/latitude.svg b/other/logos/latitude.svg new file mode 100644 index 000000000..489d9ebc7 --- /dev/null +++ b/other/logos/latitude.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 744272242..0076c941a 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -8,7 +8,7 @@ @use('App\Models\InstanceSettings') @php - $instanceSettings = InstanceSettings::first(); + $instanceSettings = \App\Models\InstanceSettings::get(); $name = null; if ($instanceSettings) { diff --git a/resources/views/livewire/project/application/configuration.blade.php b/resources/views/livewire/project/application/configuration.blade.php index 639776730..feb8c5679 100644 --- a/resources/views/livewire/project/application/configuration.blade.php +++ b/resources/views/livewire/project/application/configuration.blade.php @@ -109,7 +109,7 @@
- +
@@ -133,7 +133,7 @@
- +
diff --git a/resources/views/livewire/project/database/configuration.blade.php b/resources/views/livewire/project/database/configuration.blade.php index aa5b8ec5e..652aec3eb 100644 --- a/resources/views/livewire/project/database/configuration.blade.php +++ b/resources/views/livewire/project/database/configuration.blade.php @@ -99,7 +99,7 @@
- +
diff --git a/resources/views/livewire/project/resource/environment-select.blade.php b/resources/views/livewire/project/resource/environment-select.blade.php index ab160dcda..c03466d28 100644 --- a/resources/views/livewire/project/resource/environment-select.blade.php +++ b/resources/views/livewire/project/resource/environment-select.blade.php @@ -1,5 +1,5 @@ - + @foreach ($environments as $environment)
@else @if (!str($resource->status)->contains('running')) -
Metrics are only available when the application is running!
+
Metrics are only available when this resource is running!
@else diff --git a/resources/views/livewire/project/shared/tags.blade.php b/resources/views/livewire/project/shared/tags.blade.php index 0de2540fd..683731780 100644 --- a/resources/views/livewire/project/shared/tags.blade.php +++ b/resources/views/livewire/project/shared/tags.blade.php @@ -1,10 +1,18 @@

Tags

-
- @if (data_get($this->resource, 'tags')) - @forelse (data_get($this->resource,'tags') as $tagId => $tag) -
+
+
+ +
+ Add +
+ @if (data_get($this->resource, 'tags') && count(data_get($this->resource, 'tags')) > 0) +

Assigned Tags

+
+ @foreach (data_get($this->resource, 'tags') as $tagId => $tag) +
{{ $tag->name }}
- @empty -
No tags yet
- @endforelse - @endif -
-
-
- + @endforeach
- Add -
- @if (count($tags) > 0) + @endif + @if (count($filteredTags) > 0)

Exisiting Tags

Click to add quickly
- @foreach ($tags as $tag) + @foreach ($filteredTags as $tag) {{ $tag->name }} @endforeach diff --git a/resources/views/livewire/project/shared/webhooks.blade.php b/resources/views/livewire/project/shared/webhooks.blade.php index 9390082c9..6b9e1c86d 100644 --- a/resources/views/livewire/project/shared/webhooks.blade.php +++ b/resources/views/livewire/project/shared/webhooks.blade.php @@ -13,13 +13,13 @@

Manual Git Webhooks

@if ($githubManualWebhook && $gitlabManualWebhook) -
+
+ label="GitHub Webhook Secret" id="githubManualWebhookSecret">
@@ -31,21 +31,19 @@ + label="GitLab Webhook Secret" id="gitlabManualWebhookSecret">
+ label="Bitbucket Webhook Secret" id="bitbucketManualWebhookSecret">
+ label="Gitea Webhook Secret" id="giteaManualWebhookSecret">
Save diff --git a/resources/views/livewire/server/form.blade.php b/resources/views/livewire/server/form.blade.php index d8144e931..5cba620dd 100644 --- a/resources/views/livewire/server/form.blade.php +++ b/resources/views/livewire/server/form.blade.php @@ -146,15 +146,13 @@

Sentinel

- @if ($server->isSentinelEnabled()) - Restart - @endif + {{-- @if ($server->isSentinelEnabled()) --}} + {{-- Restart --}} + {{-- @endif --}}
-
+
Metrics are disabled until a few bugs are fixed.
+ {{--
- {{-- - Check Port for Server API --}}
@@ -166,7 +164,7 @@
-
+
--}} @endif
diff --git a/templates/compose/twenty.yaml b/templates/compose/twenty.yaml index aa340f669..f643b9623 100644 --- a/templates/compose/twenty.yaml +++ b/templates/compose/twenty.yaml @@ -12,13 +12,15 @@ services: - SERVER_URL=$SERVICE_FQDN_TWENTY - FRONT_BASE_URL=$SERVICE_FQDN_TWENTY - ENABLE_DB_MIGRATIONS=true - - SIGN_IN_PREFILLED=false + - CACHE_STORAGE_TYPE=${CACHE_STORAGE_TYPE:-redis} + - REDIS_HOST=redis + - REDIS_PORT=6379 - - STORAGE_TYPE=${STORAGE_TYPE:-local} - - STORAGE_S3_REGION=$STORAGE_S3_REGION - - STORAGE_S3_NAME=$STORAGE_S3_NAME - - STORAGE_S3_ENDPOINT=$STORAGE_S3_ENDPOINT + # https://twenty.com/developers/section/self-hosting/self-hosting-var#security + - API_RATE_LIMITING_TTL=${API_RATE_LIMITING_TTL:-100} + - API_RATE_LIMITING_LIMIT=${API_RATE_LIMITING_LIMIT:-100} + # https://twenty.com/developers/section/self-hosting/self-hosting-var#tokens - ACCESS_TOKEN_SECRET=$SERVICE_BASE64_32_ACCESS - LOGIN_TOKEN_SECRET=$SERVICE_BASE64_32_LOGIN - REFRESH_TOKEN_SECRET=$SERVICE_BASE64_32_REFRESH @@ -26,6 +28,26 @@ services: - POSTGRES_ADMIN_PASSWORD=$SERVICE_PASSWORD_POSTGRES - PG_DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_POSTGRES@postgres:5432/default + # https://twenty.com/developers/section/self-hosting/self-hosting-var#auth + - IS_SIGN_UP_DISABLED=${IS_SIGN_UP_DISABLED:-false} + - PASSWORD_RESET_TOKEN_EXPIRES_IN=${PASSWORD_RESET_TOKEN_EXPIRES_IN:-5m} + + # https://twenty.com/developers/section/self-hosting/self-hosting-var#workspace-cleaning + - WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION=$WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION + - WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION=$WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION + + # https://twenty.com/developers/section/self-hosting/self-hosting-var#captcha + - STORAGE_TYPE=${STORAGE_TYPE:-local} + - STORAGE_S3_REGION=$STORAGE_S3_REGION + - STORAGE_S3_NAME=$STORAGE_S3_NAME + - STORAGE_S3_ENDPOINT=$STORAGE_S3_ENDPOINT + - STORAGE_S3_ACCESS_KEY_ID=$STORAGE_S3_ACCESS_KEY_ID + - STORAGE_S3_SECRET_ACCESS_KEY=$STORAGE_S3_SECRET_ACCESS_KEY + + # https://twenty.com/developers/section/self-hosting/self-hosting-var#message-queue + - MESSAGE_QUEUE_TYPE=$MESSAGE_QUEUE_TYPE + + # https://twenty.com/developers/section/self-hosting/self-hosting-var#email - EMAIL_FROM_ADDRESS=$EMAIL_FROM_ADDRESS - EMAIL_FROM_NAME=$EMAIL_FROM_NAME - EMAIL_SYSTEM_ADDRESS=$EMAIL_SYSTEM_ADDRESS @@ -35,10 +57,12 @@ services: - EMAIL_SMTP_USER=$EMAIL_SMTP_USER - EMAIL_SMTP_PASSWORD=$EMAIL_SMTP_PASSWORD + # https://twenty.com/developers/section/self-hosting/self-hosting-var#debug-/-development + - SIGN_IN_PREFILLED=false + - DEBUG_MODE=${DEBUG_MODE:-false} + + # https://twenty.com/developers/section/self-hosting/self-hosting-var#telemetry - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false} - - CACHE_STORAGE_TYPE=${CACHE_STORAGE_TYPE:-redis} - - REDIS_HOST=redis - - REDIS_PORT=6379 depends_on: postgres: condition: service_healthy diff --git a/versions.json b/versions.json index 64ce34840..78c113fbd 100644 --- a/versions.json +++ b/versions.json @@ -1,7 +1,7 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.309" + "version": "4.0.0-beta.313" } } } \ No newline at end of file