diff --git a/app/Http/Livewire/Application/Heading.php b/app/Http/Livewire/Application/Heading.php index 44864f81b..b062862b5 100644 --- a/app/Http/Livewire/Application/Heading.php +++ b/app/Http/Livewire/Application/Heading.php @@ -4,7 +4,7 @@ namespace App\Http\Livewire\Application; use App\Jobs\ApplicationContainerStatusJob; use App\Models\Application; -use App\Notifications\Notifications\Application\ApplicationStoppedNotification; +use App\Notifications\Application\StatusChanged; use Livewire\Component; use Visus\Cuid2\Cuid2; @@ -55,11 +55,11 @@ class Heading extends Component ); $this->application->status = 'stopped'; $this->application->save(); - $this->application->environment->project->team->notify(new ApplicationStoppedNotification($this->application)); + $this->application->environment->project->team->notify(new StatusChanged($this->application)); } protected function setDeploymentUuid() { $this->deploymentUuid = new Cuid2(7); $this->parameters['deployment_uuid'] = $this->deploymentUuid; } -} +} \ No newline at end of file diff --git a/app/Http/Livewire/Notifications/DiscordSettings.php b/app/Http/Livewire/Notifications/DiscordSettings.php index cabfcfecc..e7dab8036 100644 --- a/app/Http/Livewire/Notifications/DiscordSettings.php +++ b/app/Http/Livewire/Notifications/DiscordSettings.php @@ -3,7 +3,7 @@ namespace App\Http\Livewire\Notifications; use App\Models\Team; -use App\Notifications\Notifications\TestNotification; +use App\Notifications\Test; use Livewire\Component; class DiscordSettings extends Component @@ -46,7 +46,7 @@ class DiscordSettings extends Component } public function sendTestNotification() { - $this->model->notify(new TestNotification('discord')); + $this->model->notify(new Test); $this->emit('success', 'Test notification sent.'); } } \ No newline at end of file diff --git a/app/Http/Livewire/Notifications/EmailSettings.php b/app/Http/Livewire/Notifications/EmailSettings.php index 31e50ea5a..1dd3746e8 100644 --- a/app/Http/Livewire/Notifications/EmailSettings.php +++ b/app/Http/Livewire/Notifications/EmailSettings.php @@ -4,12 +4,13 @@ namespace App\Http\Livewire\Notifications; use App\Models\InstanceSettings; use App\Models\Team; -use App\Notifications\Notifications\TestNotification; +use App\Notifications\Test; use Livewire\Component; class EmailSettings extends Component { public Team $model; + public string $emails; protected $rules = [ 'model.smtp_enabled' => 'nullable|boolean', @@ -48,6 +49,7 @@ class EmailSettings extends Component public function mount() { $this->decrypt(); + $this->emails = auth()->user()->email; } public function copyFromInstanceSettings() { @@ -93,8 +95,8 @@ class EmailSettings extends Component } public function sendTestNotification() { - $this->model->notify(new TestNotification('smtp')); - $this->emit('success', 'Test notification sent.'); + $this->model->notify(new Test($this->emails)); + $this->emit('success', 'Test Email sent successfully.'); } public function instantSave() { diff --git a/app/Http/Livewire/Settings/Email.php b/app/Http/Livewire/Settings/Email.php index 5fb7d85ac..cd096e194 100644 --- a/app/Http/Livewire/Settings/Email.php +++ b/app/Http/Livewire/Settings/Email.php @@ -3,14 +3,14 @@ namespace App\Http\Livewire\Settings; use App\Models\InstanceSettings; -use App\Notifications\TransactionalEmails\TestEmail; +use App\Notifications\TransactionalEmails\Test; use Illuminate\Support\Facades\Notification; use Livewire\Component; class Email extends Component { public InstanceSettings $settings; - + public string $emails; protected $rules = [ 'settings.smtp_enabled' => 'nullable|boolean', 'settings.smtp_host' => 'required', @@ -35,6 +35,7 @@ class Email extends Component public function mount() { $this->decrypt(); + $this->emails = auth()->user()->email; } public function instantSave() { @@ -46,9 +47,9 @@ class Email extends Component $this->validate(); } } - public function testNotification() + public function sendTestNotification() { - $this->settings->notify(new TestEmail); + $this->settings->notify(new Test($this->emails)); $this->emit('success', 'Test email sent.'); } private function decrypt() diff --git a/app/Http/Livewire/Team/InviteLink.php b/app/Http/Livewire/Team/InviteLink.php index e80e5338b..b197334cb 100644 --- a/app/Http/Livewire/Team/InviteLink.php +++ b/app/Http/Livewire/Team/InviteLink.php @@ -4,7 +4,7 @@ namespace App\Http\Livewire\Team; use App\Models\TeamInvitation; use App\Models\User; -use App\Notifications\TransactionalEmails\InvitationLinkEmail; +use App\Notifications\TransactionalEmails\InvitationLink; use Livewire\Component; use Visus\Cuid2\Cuid2; @@ -58,7 +58,7 @@ class InviteLink extends Component 'via' => $isEmail ? 'email' : 'link', ]); if ($isEmail) { - $user->first()->notify(new InvitationLinkEmail()); + $user->first()->notify(new InvitationLink); $this->emit('success', 'Invitation sent via email successfully.'); } else { $this->emit('success', 'Invitation link generated.'); @@ -76,4 +76,4 @@ class InviteLink extends Component { $this->generate_invite_link(); } -} +} \ No newline at end of file diff --git a/app/Jobs/ApplicationContainerStatusJob.php b/app/Jobs/ApplicationContainerStatusJob.php index ac742c5e0..7f177af3d 100644 --- a/app/Jobs/ApplicationContainerStatusJob.php +++ b/app/Jobs/ApplicationContainerStatusJob.php @@ -4,7 +4,7 @@ namespace App\Jobs; use App\Models\Application; use App\Models\ApplicationPreview; -use App\Notifications\Notifications\Application\ApplicationStoppedNotification; +use App\Notifications\Application\StatusChanged; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; @@ -36,7 +36,7 @@ class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique try { $status = get_container_status(server: $this->application->destination->server, container_id: $this->container_name, throwError: false); if ($this->application->status === 'running' && $status === 'stopped') { - $this->application->environment->project->team->notify(new ApplicationStoppedNotification($this->application)); + $this->application->environment->project->team->notify(new StatusChanged($this->application)); } if ($this->pull_request_id) { @@ -51,4 +51,4 @@ class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique ray($e->getMessage()); } } -} +} \ No newline at end of file diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 0068e46e7..945de85cb 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -12,8 +12,8 @@ use App\Models\GitlabApp; use App\Models\Server; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; -use App\Notifications\Notifications\Application\DeployedSuccessfullyNotification; -use App\Notifications\Notifications\Application\DeployedWithErrorNotification; +use App\Notifications\Application\DeploymentSuccess; +use App\Notifications\Application\DeploymentFailed; use App\Traits\ExecuteRemoteCommand; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -224,10 +224,10 @@ class ApplicationDeploymentJob implements ShouldQueue } queue_next_deployment($this->application); if ($status === ApplicationDeploymentStatus::FINISHED->value) { - $this->application->environment->project->team->notify(new DeployedSuccessfullyNotification($this->application, $this->deployment_uuid, $this->preview)); + $this->application->environment->project->team->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); } if ($status === ApplicationDeploymentStatus::FAILED->value) { - $this->application->environment->project->team->notify(new DeployedWithErrorNotification($this->application, $this->deployment_uuid, $this->preview)); + $this->application->environment->project->team->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview)); } } private function start_by_compose_file() @@ -266,13 +266,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); listen 80; listen [::]:80; server_name localhost; - + location / { root /usr/share/nginx/html; index index.html; try_files \$uri \$uri.html \$uri/index.html \$uri/ /index.html =404; } - + error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; @@ -656,4 +656,4 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); ); $this->commit = $this->saved_outputs->get('git_commit_sha'); } -} +} \ No newline at end of file diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php index 88aba9c96..ef7859786 100644 --- a/app/Models/InstanceSettings.php +++ b/app/Models/InstanceSettings.php @@ -15,9 +15,9 @@ class InstanceSettings extends Model implements SendsEmail protected $casts = [ 'resale_license' => 'encrypted', ]; - public function routeNotificationForEmail(string $attribute = 'test_recipients') + public function getRecepients($notification) { - $recipients = data_get($this,'smtp',''); + $recipients = data_get($notification,'emails',null); if (is_null($recipients) || $recipients === '') { return []; } diff --git a/app/Models/Team.php b/app/Models/Team.php index 4e624cd14..ccd7613a6 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -23,11 +23,14 @@ class Team extends Model implements SendsDiscord, SendsEmail { return data_get($this, 'discord_webhook_url', null); } - public function routeNotificationForEmail(string $attribute = 'recipients') + public function getRecepients($notification) { - $recipients = data_get($this, 'smtp_recipients', ''); - if (is_null($recipients) || $recipients === '') { - return []; + $recipients = data_get($notification,'emails',null); + ray($recipients); + if (is_null($recipients)) { + $recipients = $this->members()->pluck('email')->toArray(); + ray($recipients); + return $recipients; } return explode(',', $recipients); } diff --git a/app/Models/User.php b/app/Models/User.php index 650fe1c0a..d9f1eaa6a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -10,6 +10,7 @@ use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; use Visus\Cuid2\Cuid2; use Laravel\Fortify\TwoFactorAuthenticatable; +use App\Notifications\TrnsactionalEmails\ResetPassword; class User extends Authenticatable implements SendsEmail { @@ -43,10 +44,14 @@ class User extends Authenticatable implements SendsEmail $user->teams()->attach($new_team, ['role' => 'owner']); }); } - public function routeNotificationForEmail() + public function getRecepients($notification) { return $this->email; } + public function sendPasswordResetNotification($token): void + { + $this->notify(new ResetPassword($token)); + } public function isAdmin() { return $this->pivot->role === 'admin' || $this->pivot->role === 'owner'; diff --git a/app/Notifications/Notifications/Application/DeployedWithErrorNotification.php b/app/Notifications/Application/DeploymentFailed.php similarity index 94% rename from app/Notifications/Notifications/Application/DeployedWithErrorNotification.php rename to app/Notifications/Application/DeploymentFailed.php index 34ad4a06a..b279d396a 100644 --- a/app/Notifications/Notifications/Application/DeployedWithErrorNotification.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -1,6 +1,6 @@ subject('❌ Pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . ' deployment failed.'); } - $mail->view('emails.application-deployed-with-error', [ + $mail->view('emails.application-deployment-failed', [ 'name' => $this->application_name, 'fqdn' => $fqdn, 'deployment_url' => $this->deployment_url, diff --git a/app/Notifications/Notifications/Application/DeployedSuccessfullyNotification.php b/app/Notifications/Application/DeploymentSuccess.php similarity index 94% rename from app/Notifications/Notifications/Application/DeployedSuccessfullyNotification.php rename to app/Notifications/Application/DeploymentSuccess.php index 866e18d79..da9c7c649 100644 --- a/app/Notifications/Notifications/Application/DeployedSuccessfullyNotification.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -1,6 +1,6 @@ preview->fqdn; $mail->subject("✅ Pull request #{$pull_request_id} of {$this->application_name} deployed successfully"); } - $mail->view('emails.application-deployed-successfully', [ + $mail->view('emails.application-deployment-success', [ 'name' => $this->application_name, 'fqdn' => $fqdn, 'deployment_url' => $this->deployment_url, @@ -97,4 +97,4 @@ class DeployedSuccessfullyNotification extends Notification implements ShouldQue } return $message; } -} +} \ No newline at end of file diff --git a/app/Notifications/Notifications/Application/ApplicationStoppedNotification.php b/app/Notifications/Application/StatusChanged.php similarity index 93% rename from app/Notifications/Notifications/Application/ApplicationStoppedNotification.php rename to app/Notifications/Application/StatusChanged.php index f51956ca1..afa8839a2 100644 --- a/app/Notifications/Notifications/Application/ApplicationStoppedNotification.php +++ b/app/Notifications/Application/StatusChanged.php @@ -1,6 +1,6 @@ fqdn; $mail->subject("⛔ {$this->application_name} has been stopped"); - $mail->view('emails.application-stopped', [ + $mail->view('emails.application-status-changes', [ 'name' => $this->application_name, 'fqdn' => $fqdn, 'application_url' => $this->application_url, @@ -72,4 +72,4 @@ class ApplicationStoppedNotification extends Notification implements ShouldQueue $message .= '[Application URL](' . $this->application_url . ')'; return $message; } -} +} \ No newline at end of file diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php index cc493be19..22b5e59a0 100644 --- a/app/Notifications/Channels/EmailChannel.php +++ b/app/Notifications/Channels/EmailChannel.php @@ -11,13 +11,13 @@ class EmailChannel public function send(SendsEmail $notifiable, Notification $notification): void { $this->bootConfigs($notifiable); + ray($notification); + $recepients = $notifiable->getRecepients($notification); - $bcc = $notifiable->routeNotificationForEmail('test_recipients'); - if (count($bcc) === 0) { - if ($notifiable instanceof \App\Models\Team) { - $bcc = $notifiable->members()->pluck('email')->toArray(); - } + if (count($recepients) === 0) { + throw new \Exception('No email recipients found'); } + $mailMessage = $notification->toMail($notifiable); Mail::send( [], @@ -27,7 +27,7 @@ class EmailChannel data_get($notifiable, 'smtp_from_address'), data_get($notifiable, 'smtp_from_name'), ) - ->bcc($bcc) + ->bcc($recepients) ->subject($mailMessage->subject) ->html((string)$mailMessage->render()) ); diff --git a/app/Notifications/Channels/SendsEmail.php b/app/Notifications/Channels/SendsEmail.php index ca72eef41..dabbb7a6d 100644 --- a/app/Notifications/Channels/SendsEmail.php +++ b/app/Notifications/Channels/SendsEmail.php @@ -4,5 +4,5 @@ namespace App\Notifications\Channels; interface SendsEmail { - public function routeNotificationForEmail(); -} + public function getRecepients($notification); +} \ No newline at end of file diff --git a/app/Notifications/Notifications/TestNotification.php b/app/Notifications/Test.php similarity index 69% rename from app/Notifications/Notifications/TestNotification.php rename to app/Notifications/Test.php index 422cc2dd9..c7fc9f714 100644 --- a/app/Notifications/Notifications/TestNotification.php +++ b/app/Notifications/Test.php @@ -1,6 +1,6 @@ type = $type; - } + public function __construct(public string|null $emails = null) + {} + public function via(object $notifiable): array { $channels = []; - - $isSmtp = $this->type === 'smtp' || is_null($this->type); - $isDiscord = $this->type === 'discord' || is_null($this->type); $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - if ($isEmailEnabled && $isSmtp) { - $channels[] = EmailChannel::class; - } - if ($isDiscordEnabled && $isDiscord) { + if ($isDiscordEnabled && empty($this->emails)) { $channels[] = DiscordChannel::class; } + if ($isEmailEnabled && !empty($this->emails)) { + $channels[] = EmailChannel::class; + } return $channels; } public function toMail(): MailMessage @@ -42,7 +37,6 @@ class TestNotification extends Notification implements ShouldQueue $mail->view('emails.test'); return $mail; } - public function toDiscord(): string { $message = 'This is a test Discord notification from Coolify.'; diff --git a/app/Notifications/TransactionalEmails/InvitationLinkEmail.php b/app/Notifications/TransactionalEmails/InvitationLink.php similarity index 93% rename from app/Notifications/TransactionalEmails/InvitationLinkEmail.php rename to app/Notifications/TransactionalEmails/InvitationLink.php index fe53c6fce..7785f92cc 100644 --- a/app/Notifications/TransactionalEmails/InvitationLinkEmail.php +++ b/app/Notifications/TransactionalEmails/InvitationLink.php @@ -11,7 +11,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; -class InvitationLinkEmail extends Notification implements ShouldQueue +class InvitationLink extends Notification implements ShouldQueue { use Queueable; public function via() @@ -33,4 +33,4 @@ class InvitationLinkEmail extends Notification implements ShouldQueue ]); return $mail; } -} +} \ No newline at end of file diff --git a/app/Notifications/TransactionalEmails/ResetPassword.php b/app/Notifications/TransactionalEmails/ResetPassword.php new file mode 100644 index 000000000..856d5b697 --- /dev/null +++ b/app/Notifications/TransactionalEmails/ResetPassword.php @@ -0,0 +1,83 @@ +settings = InstanceSettings::get(); + $this->token = $token; + } + public function via($notifiable) + { + if ($this->settings->smtp_enabled){ + $password = data_get($this->settings, 'smtp_password'); + if ($password) $password = decrypt($password); + + config()->set('mail.default', 'smtp'); + config()->set('mail.mailers.smtp', [ + "transport" => "smtp", + "host" => data_get($this->settings, 'smtp_host'), + "port" => data_get($this->settings, 'smtp_port'), + "encryption" => data_get($this->settings, 'smtp_encryption'), + "username" => data_get($this->settings, 'smtp_username'), + "password" => $password, + "timeout" => data_get($this->settings, 'smtp_timeout'), + "local_domain" => null, + ]); + return ['mail']; + } + throw new \Exception('SMTP is not enabled'); + + } + + public function toMail($notifiable) + { + if (static::$toMailCallback) { + return call_user_func(static::$toMailCallback, $notifiable, $this->token); + } + + return $this->buildMailMessage($this->resetUrl($notifiable)); + } + protected function buildMailMessage($url) + { + $mail = new MailMessage(); + $mail->from( + data_get($this->settings, 'smtp_from_address'), + data_get($this->settings, 'smtp_from_name'), + ); + $mail->subject('Reset Password'); + $mail->view('emails.reset-password', ['url' => $url,'count' => config('auth.passwords.'.config('auth.defaults.passwords').'.expire')]); + return $mail; + } + protected function resetUrl($notifiable) + { + if (static::$createUrlCallback) { + return call_user_func(static::$createUrlCallback, $notifiable, $this->token); + } + + return url(route('password.reset', [ + 'token' => $this->token, + 'email' => $notifiable->getEmailForPasswordReset(), + ], false)); + } + public static function createUrlUsing($callback) + { + static::$createUrlCallback = $callback; + } + public static function toMailUsing($callback) + { + static::$toMailCallback = $callback; + } +} \ No newline at end of file diff --git a/app/Notifications/TransactionalEmails/TestEmail.php b/app/Notifications/TransactionalEmails/Test.php similarity index 82% rename from app/Notifications/TransactionalEmails/TestEmail.php rename to app/Notifications/TransactionalEmails/Test.php index 4dbd7f1c3..a07130ad2 100644 --- a/app/Notifications/TransactionalEmails/TestEmail.php +++ b/app/Notifications/TransactionalEmails/Test.php @@ -8,13 +8,18 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; -class TestEmail extends Notification implements ShouldQueue +class Test extends Notification implements ShouldQueue { use Queueable; + + public function __construct(public string $emails) + {} + public function via(): array { return [EmailChannel::class]; } + public function toMail(): MailMessage { $mail = new MailMessage(); @@ -22,4 +27,4 @@ class TestEmail extends Notification implements ShouldQueue $mail->view('emails.test'); return $mail; } -} +} \ No newline at end of file diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php index b22bc11dc..1e517ea3f 100644 --- a/app/View/Components/Forms/Input.php +++ b/app/View/Components/Forms/Input.php @@ -30,7 +30,7 @@ class Input extends Component if (is_null($this->id)) $this->id = new Cuid2(7); if (is_null($this->name)) $this->name = $this->id; - $this->label = Str::title($this->label); + // $this->label = Str::title($this->label); return view('components.forms.input'); } -} +} \ No newline at end of file diff --git a/resources/views/emails/application-deployed-with-error.blade.php b/resources/views/emails/application-deployment-failed.php similarity index 100% rename from resources/views/emails/application-deployed-with-error.blade.php rename to resources/views/emails/application-deployment-failed.php diff --git a/resources/views/emails/application-deployed-successfully.blade.php b/resources/views/emails/application-deployment-success.php similarity index 100% rename from resources/views/emails/application-deployed-successfully.blade.php rename to resources/views/emails/application-deployment-success.php diff --git a/resources/views/emails/application-status-changes.blade.php b/resources/views/emails/application-status-changes.blade.php new file mode 100644 index 000000000..c2b73ef92 --- /dev/null +++ b/resources/views/emails/application-status-changes.blade.php @@ -0,0 +1,2 @@ +{{ $name }} has been stopped.

+Open in Coolify

diff --git a/resources/views/emails/application-stopped.blade.php b/resources/views/emails/application-stopped.blade.php deleted file mode 100644 index bce6e824f..000000000 --- a/resources/views/emails/application-stopped.blade.php +++ /dev/null @@ -1,2 +0,0 @@ -{{ $name }} has been stopped.

-View Application URL

diff --git a/resources/views/emails/reset-password.blade.php b/resources/views/emails/reset-password.blade.php index 0a521d43d..7a0c9788c 100644 --- a/resources/views/emails/reset-password.blade.php +++ b/resources/views/emails/reset-password.blade.php @@ -2,4 +2,7 @@ Hello,

A password reset requested for your email address on "{{ config('app.name') }}".

-Please click the following link to reset your password: Password Reset +Please click the following link to reset your password: Password + Reset

+ +This password reset link will expire in "{{ $count }}" minutes. diff --git a/resources/views/livewire/notifications/discord-settings.blade.php b/resources/views/livewire/notifications/discord-settings.blade.php index f5546da17..fbc17cb94 100644 --- a/resources/views/livewire/notifications/discord-settings.blade.php +++ b/resources/views/livewire/notifications/discord-settings.blade.php @@ -23,12 +23,11 @@

Subscribe to events

@if (isDev()) - + @endif
Applications
+ label="Deployments" />
diff --git a/resources/views/livewire/notifications/email-settings.blade.php b/resources/views/livewire/notifications/email-settings.blade.php index 082405fdb..436b09cf1 100644 --- a/resources/views/livewire/notifications/email-settings.blade.php +++ b/resources/views/livewire/notifications/email-settings.blade.php @@ -3,7 +3,7 @@