Sending e-mails and settings on Team.

This commit is contained in:
Joao Patricio 2023-05-25 17:27:52 +01:00
parent aea6bced69
commit f23a760aac
23 changed files with 344 additions and 267 deletions

View File

@ -0,0 +1,75 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use function Termwind\ask;
use function Termwind\render;
use function Termwind\style;
class NotifyDemo extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:demo-notify {channel?}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send a demo notification, to a given channel. Run to see options.';
/**
* Execute the console command.
*/
public function handle()
{
$channel = $this->argument('channel');
if (blank($channel)) {
$this->showHelp();
return;
}
ray($channel);
}
private function showHelp()
{
style('coolify')->color('#9333EA');
style('title-box')->apply('mt-1 px-2 py-1 bg-coolify');
render(<<<'HTML'
<div>
<div class="title-box">
Coolify
</div>
<p class="ml-1 mt-1 ">
Demo Notify <strong class="text-coolify">=></strong> Send a demo notification to a given channel.
</p>
<p class="ml-1 mt-1 bg-coolify px-1">
php artisan app:demo-notify {channel}
</p>
<div class="my-1">
<div class="text-yellow-500"> Channels: </div>
<ul class="text-coolify">
<li>email</li>
<li>slack</li>
<li>discord</li>
<li>telegram</li>
</ul>
</div>
</div>
HTML);
ask(<<<'HTML'
<div class="mr-1">
In which manner you wish a <strong class="text-coolify">coolified</strong> notification?
</div>
HTML, ['email', 'slack', 'discord', 'telegram']);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Http\Livewire\Notifications;
use App\Models\Server;
use App\Models\Team;
use App\Notifications\DemoNotification;
use Illuminate\Support\Facades\Notification;
use Livewire\Component;
class DiscordSettings extends Component
{
public Team|Server $model;
protected $rules = [
'model.extra_attributes.discord_webhook' => 'nullable|url',
'model.extra_attributes.discord_active' => 'nullable|boolean',
];
protected $validationAttributes = [
'model.extra_attributes.discord_webhook' => 'Discord Webhook',
];
public function mount($model)
{
//
}
public function submit()
{
$this->resetErrorBag();
$this->validate();
$this->model->save();
if ( is_a($this->model, Team::class)) {
session(['currentTeam' => $this->model]);
}
}
public function sentTestMessage()
{
Notification::send($this->model, new DemoNotification);
}
public function render()
{
return view('livewire.notifications.discord-settings');
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Http\Livewire\Notifications;
use App\Models\Server;
use App\Models\Team;
use Livewire\Component;
class EmailSettings extends Component
{
public Team|Server $model;
protected $rules = [
'model.extra_attributes.smtp_active' => 'nullable|boolean',
'model.extra_attributes.recipients' => 'nullable',
'model.extra_attributes.smtp_host' => 'nullable',
'model.extra_attributes.smtp_port' => 'nullable',
'model.extra_attributes.smtp_encryption' => 'nullable',
'model.extra_attributes.smtp_username' => 'nullable',
'model.extra_attributes.smtp_password' => 'nullable',
'model.extra_attributes.smtp_timeout' => 'nullable',
];
protected $validationAttributes = [
'model.extra_attributes.recipients' => 'Recipients',
'model.extra_attributes.smtp_host' => 'Host',
'model.extra_attributes.smtp_port' => 'Port',
'model.extra_attributes.smtp_encryption' => 'Encryption',
'model.extra_attributes.smtp_username' => 'Username',
'model.extra_attributes.smtp_password' => 'Password',
'model.extra_attributes.smtp_timeout' => 'Timeout',
];
public function mount($model)
{
//
}
public function submit()
{
$this->resetErrorBag();
$this->validate();
$this->model->save();
if ( is_a($this->model, Team::class)) {
session(['currentTeam' => $this->model]);
}
}
public function sendTestNotification()
{
}
public function render()
{
return view('livewire.notifications.email-settings');
}
}

View File

@ -1,38 +0,0 @@
<?php
namespace App\Http\Livewire\Settings;
use App\Models\InstanceSettings as ModelsInstanceSettings;
use App\Notifications\TestMessage;
use Illuminate\Support\Facades\Notification;
use Livewire\Component;
class DiscordNotifications extends Component
{
public ModelsInstanceSettings $settings;
protected $rules = [
'settings.extra_attributes.discord_webhook' => 'nullable|url',
];
protected $validationAttributes = [
'settings.extra_attributes.discord_webhook' => 'Discord Webhook',
];
public function mount($settings)
{
//
}
public function submit()
{
$this->resetErrorBag();
$this->validate();
$this->settings->save();
}
public function sentTestMessage()
{
Notification::send(auth()->user(), new TestMessage);
}
public function render()
{
return view('livewire.settings.discord-notifications');
}
}

View File

@ -1,49 +0,0 @@
<?php
namespace App\Http\Livewire\Settings;
use App\Models\InstanceSettings as ModelsInstanceSettings;
use App\Notifications\TestMessage;
use Illuminate\Support\Facades\Notification;
use Livewire\Component;
class EmailNotifications extends Component
{
public ModelsInstanceSettings $settings;
protected $rules = [
'settings.extra_attributes.smtp_host' => 'nullable',
'settings.extra_attributes.smtp_port' => 'nullable',
'settings.extra_attributes.smtp_encryption' => 'nullable',
'settings.extra_attributes.smtp_username' => 'nullable',
'settings.extra_attributes.smtp_password' => 'nullable',
'settings.extra_attributes.smtp_timeout' => 'nullable',
];
protected $validationAttributes = [
'settings.extra_attributes.smtp_host' => 'Host',
'settings.extra_attributes.smtp_port' => 'Port',
'settings.extra_attributes.smtp_encryption' => 'Encryption',
'settings.extra_attributes.smtp_username' => 'Username',
'settings.extra_attributes.smtp_password' => 'Password',
'settings.extra_attributes.smtp_timeout' => 'Timeout',
];
public function mount($settings)
{
ray($settings);
//
}
public function submit()
{
$this->resetErrorBag();
$this->validate();
$this->settings->save();
}
public function sentTestMessage()
{
}
public function render()
{
return view('livewire.settings.email-notifications');
}
}

View File

@ -1,53 +0,0 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class ExampleMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct()
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Example Mail',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'emails.example',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@ -2,21 +2,10 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
class InstanceSettings extends Model class InstanceSettings extends Model
{ {
public $casts = [
'extra_attributes' => SchemalessAttributes::class,
];
public function scopeWithExtraAttributes(): Builder
{
return $this->extra_attributes->modelScope();
}
public static function get() public static function get()
{ {
return InstanceSettings::findOrFail(0); return InstanceSettings::findOrFail(0);

View File

@ -2,28 +2,59 @@
namespace App\Models; namespace App\Models;
class Team extends BaseModel use App\Notifications\Channels\SendsCoolifyEmail;
use App\Notifications\Channels\SendsDiscord;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Notifications\Notifiable;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
class Team extends BaseModel implements SendsDiscord, SendsCoolifyEmail
{ {
use Notifiable;
protected $casts = [ protected $casts = [
'extra_attributes' => SchemalessAttributes::class,
'personal_team' => 'boolean', 'personal_team' => 'boolean',
]; ];
protected $fillable = [ protected $fillable = [
'id', 'id',
'name', 'name',
'personal_team' 'personal_team',
'extra_attributes',
]; ];
public function routeNotificationForDiscord()
{
return $this->extra_attributes->get('discord_webhook');
}
public function routeNotificationForCoolifyEmail()
{
$recipients = $this->extra_attributes->get('recipients', '');
return explode(PHP_EOL, $recipients);
}
public function scopeWithExtraAttributes(): Builder
{
return $this->extra_attributes->modelScope();
}
public function projects() public function projects()
{ {
return $this->hasMany(Project::class); return $this->hasMany(Project::class);
} }
public function servers() public function servers()
{ {
return $this->hasMany(Server::class); return $this->hasMany(Server::class);
} }
public function applications() public function applications()
{ {
return $this->hasManyThrough(Application::class, Project::class); return $this->hasManyThrough(Application::class, Project::class);
} }
public function privateKeys() public function privateKeys()
{ {
return $this->hasMany(PrivateKey::class); return $this->hasMany(PrivateKey::class);

View File

@ -0,0 +1,41 @@
<?php
namespace App\Notifications\Channels;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
class CoolifyEmailChannel
{
/**
* Send the given notification.
*/
public function send(SendsCoolifyEmail $notifiable, Notification $notification): void
{
$this->bootConfigs($notifiable);
$bcc = $notifiable->routeNotificationForCoolifyEmail();
$mailMessage = $notification->toMail($notifiable);
Mail::send([], [], fn(Message $message) => $message
->from('ask@me.com', 'My Coolify Instance')
->bcc($bcc)
->subject($mailMessage->subject)
->html((string) $mailMessage->render())
);
}
private function bootConfigs($notifiable): void
{
config()->set('mail.mailers.smtp', [
"transport" => "smtp",
"host" => $notifiable->extra_attributes?->get('smtp_host'),
"port" => $notifiable->extra_attributes?->get('smtp_port'),
"encryption" => $notifiable->extra_attributes?->get('smtp_encryption'),
"username" => $notifiable->extra_attributes?->get('smtp_username'),
"password" => $notifiable->extra_attributes?->get('smtp_password'),
"timeout" => $notifiable->extra_attributes?->get('smtp_timeout'),
"local_domain" => null,
]);
}
}

View File

@ -3,7 +3,6 @@
namespace App\Notifications\Channels; namespace App\Notifications\Channels;
use App\Jobs\SendMessageToDiscordJob; use App\Jobs\SendMessageToDiscordJob;
use App\Models\InstanceSettings;
use Illuminate\Notifications\Notification; use Illuminate\Notifications\Notification;
class DiscordChannel class DiscordChannel
@ -11,14 +10,11 @@ class DiscordChannel
/** /**
* Send the given notification. * Send the given notification.
*/ */
public function send(object $notifiable, Notification $notification): void public function send(SendsDiscord $notifiable, Notification $notification): void
{ {
$message = $notification->toDiscord($notifiable); $message = $notification->toDiscord($notifiable);
$webhookUrl = data_get( $webhookUrl = $notifiable->routeNotificationForDiscord();
InstanceSettings::get(),
'extra_attributes.discord_webhook'
);
dispatch(new SendMessageToDiscordJob($message, $webhookUrl)); dispatch(new SendMessageToDiscordJob($message, $webhookUrl));
} }

View File

@ -0,0 +1,8 @@
<?php
namespace App\Notifications\Channels;
interface SendsCoolifyEmail
{
public function routeNotificationForCoolifyEmail();
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Notifications\Channels;
interface SendsDiscord
{
public function routeNotificationForDiscord();
}

View File

@ -2,13 +2,14 @@
namespace App\Notifications; namespace App\Notifications;
use App\Notifications\Channels\CoolifyEmailChannel;
use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\DiscordChannel;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification; use Illuminate\Notifications\Notification;
class TestMessage extends Notification class DemoNotification extends Notification implements ShouldQueue
{ {
use Queueable; use Queueable;
@ -27,7 +28,10 @@ public function __construct()
*/ */
public function via(object $notifiable): array public function via(object $notifiable): array
{ {
return ['mail', DiscordChannel::class]; $channels = [];
$notifiable->extra_attributes?->get('smtp_active') && $channels[] = CoolifyEmailChannel::class;
$notifiable->extra_attributes?->get('discord_active') && $channels[] = DiscordChannel::class;
return $channels;
} }
/** /**
@ -35,33 +39,16 @@ public function via(object $notifiable): array
*/ */
public function toMail(object $notifiable): MailMessage public function toMail(object $notifiable): MailMessage
{ {
$smtp = [
"transport" => "smtp",
"host" => "mailpit",
"port" => 1025,
"encryption" => 'tls',
"username" => null,
"password" => null,
"timeout" => null,
"local_domain" => null,
];
config()->set('mail.mailers.smtp', $smtp);
\Illuminate\Support\Facades\Mail::mailer('smtp')
->to('ask@me.com')
->send(new \App\Mail\ExampleMail);
return (new MailMessage) return (new MailMessage)
->subject('Coolify demo notification')
->line('Welcome to Coolify!') ->line('Welcome to Coolify!')
->error()
->action('Go to dashboard', url('/')) ->action('Go to dashboard', url('/'))
->line('We need your attention for disk usage.'); ->line('We need your attention for disk usage.');
} }
public function toDiscord(object $notifiable): string public function toDiscord(object $notifiable): string
{ {
ray('1111');
return 'Welcome to Coolify! We need your attention for disk usage. [Go to dashboard]('.url('/').')'; return 'Welcome to Coolify! We need your attention for disk usage. [Go to dashboard]('.url('/').')';
} }

View File

@ -3,13 +3,11 @@
namespace App\Providers; namespace App\Providers;
use App\Jobs\CoolifyTask; use App\Jobs\CoolifyTask;
use Illuminate\Mail\MailManager;
use Illuminate\Queue\Events\JobProcessed; use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Queue;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@ -28,10 +26,6 @@ public function register(): void
*/ */
public function boot(): void public function boot(): void
{ {
if (! $this->app->environment('production')) {
\Illuminate\Support\Facades\Mail::alwaysTo('noone@example.com');
}
Queue::after(function (JobProcessed $event) { Queue::after(function (JobProcessed $event) {
// @TODO: Remove `coolify-builder` container after the remoteProcess job is finishged and remoteProcess->type == `deployment`. // @TODO: Remove `coolify-builder` container after the remoteProcess job is finishged and remoteProcess->type == `deployment`.
if ($event->job->resolveName() === CoolifyTask::class) { if ($event->job->resolveName() === CoolifyTask::class) {

View File

@ -16,6 +16,7 @@ public function up(): void
$table->string('uuid')->unique(); $table->string('uuid')->unique();
$table->string('name'); $table->string('name');
$table->boolean('personal_team')->default(false); $table->boolean('personal_team')->default(false);
$table->schemalessAttributes('extra_attributes');
$table->timestamps(); $table->timestamps();
}); });
} }

View File

@ -20,16 +20,11 @@ public function up(): void
$table->integer('public_port_min')->default(9000); $table->integer('public_port_min')->default(9000);
$table->integer('public_port_max')->default(9100); $table->integer('public_port_max')->default(9100);
// $table->string('custom_dns_servers')->default('1.1.1.1,8.8.8.8'); // $table->string('custom_dns_servers')->default('1.1.1.1,8.8.8.8');
$table->boolean('do_not_track')->default(false); $table->boolean('do_not_track')->default(false);
$table->boolean('is_auto_update_enabled')->default(true); $table->boolean('is_auto_update_enabled')->default(true);
// $table->boolean('is_dns_check_enabled')->default(true); // $table->boolean('is_dns_check_enabled')->default(true);
$table->boolean('is_registration_enabled')->default(true); $table->boolean('is_registration_enabled')->default(true);
$table->boolean('is_https_forced')->default(true); $table->boolean('is_https_forced')->default(true);
$table->schemalessAttributes('extra_attributes');
$table->timestamps(); $table->timestamps();
}); });
} }

View File

@ -0,0 +1,23 @@
<div class="">
<div class="text-xl">Discord</div>
<div class="mt-2"></div>
<form wire:submit.prevent='submit' class="flex flex-col">
<div class="flex flex-col gap-2 xl:flex-row w-96">
<x-inputs.input type="checkbox" id="model.extra_attributes.discord_active" label="Active?" />
</div>
<div class="flex flex-col gap-2 xl:flex-row w-96">
<x-inputs.input id="model.extra_attributes.discord_webhook" label="Discord Webhook" />
</div>
<div>
<x-inputs.button class="w-16 mt-4" type="submit">
Submit
</x-inputs.button>
<x-inputs.button
class="mt-4 btn btn-xs no-animation normal-case text-white btn-primary"
wire:click="sentTestMessage"
>
Send test message
</x-inputs.button>
</div>
</form>
</div>

View File

@ -0,0 +1,43 @@
<div class="mt-10">
<div class="text-xl">E-mail - SMTP</div>
<div class="mt-2"></div>
<form wire:submit.prevent='submit' class="flex flex-col">
<div class="flex flex-col w-96">
<x-inputs.input type="checkbox" id="model.extra_attributes.smtp_active" label="Active?" />
</div>
<div class="flex flex-col gap-2 xl:flex-row">
<div class="flex flex-col w-96">
<x-inputs.textarea
id="model.extra_attributes.recipients"
helper="E-mails, one per line"
label="Recipients" />
</div>
</div>
<div class="flex flex-col gap-2 xl:flex-row">
<div class="flex flex-col w-96">
<x-inputs.input id="model.extra_attributes.smtp_host" label="Host" />
<x-inputs.input id="model.extra_attributes.smtp_port" label="Port" />
<x-inputs.input id="model.extra_attributes.smtp_encryption" label="Encryption" />
</div>
<div class="flex flex-col w-96">
<x-inputs.input id="model.extra_attributes.smtp_username" label="Username" />
<x-inputs.input id="model.extra_attributes.smtp_password" label="Password" />
<x-inputs.input id="model.extra_attributes.smtp_timeout" label="Timeout" />
</div>
<div class="flex flex-col w-96">
<x-inputs.input id="model.extra_attributes.test_address" label="Send test e-mails to" />
</div>
</div>
<div class="flex">
<x-inputs.button class="w-16 mt-4" type="submit">
Submit
</x-inputs.button>
<x-inputs.button
class="mt-4 btn btn-xs no-animation normal-case text-white btn-primary"
wire:click="sendTestNotification"
>
Send test message
</x-inputs.button>
</div>
</form>
</div>

View File

@ -1,15 +0,0 @@
<div class="">
<form wire:submit.prevent='submit' class="flex flex-col">
<div class="flex flex-col gap-2 xl:flex-row w-96">
<x-inputs.input id="settings.extra_attributes.discord_webhook" label="Discord Webhook" />
</div>
<div>
<x-inputs.button class="w-16 mt-4" type="submit">
Submit
</x-inputs.button>
<x-inputs.button class="mt-4 btn btn-xs no-animation normal-case text-white btn-primary" wire:click="sentTestMessage">
Send test message
</x-inputs.button>
</div>
</form>
</div>

View File

@ -1,27 +0,0 @@
<div class="mt-10">
<form wire:submit.prevent='submit' class="flex flex-col">
<div class="flex flex-col gap-2 xl:flex-row">
<div class="flex flex-col w-96">
<x-inputs.input id="settings.extra_attributes.smtp_host" label="Host" />
<x-inputs.input id="settings.extra_attributes.smtp_port" label="Port" />
<x-inputs.input id="settings.extra_attributes.smtp_encryption" label="Encryption" />
</div>
<div class="flex flex-col w-96">
<x-inputs.input id="settings.extra_attributes.smtp_username" label="Username" />
<x-inputs.input id="settings.extra_attributes.smtp_password" label="Password" />
<x-inputs.input id="settings.extra_attributes.smtp_timeout" label="Timeout" />
</div>
</div>
<div class="flex">
<x-inputs.button class="w-16 mt-4" type="submit">
Submit
</x-inputs.button>
</div>
<div class="mt-10 flex flex-col w-96">
<x-inputs.input id="settings.extra_attributes.smtp_username" label="Send a test e-mail to:" />
<x-inputs.button class="mt-4 btn btn-xs no-animation normal-case text-white btn-primary" wire:click="sentTestMessage">
Send test message
</x-inputs.button>
</div>
</form>
</div>

View File

@ -4,11 +4,4 @@
<h3>General</h3> <h3>General</h3>
<livewire:settings.form :settings="$settings" /> <livewire:settings.form :settings="$settings" />
<div class="h-12"></div>
<h3>Notifications</h3>
<livewire:settings.discord-notifications :settings="$settings" />
<livewire:settings.email-notifications :settings="$settings" />
<div class="h-12"></div>
</x-layout> </x-layout>

View File

@ -3,6 +3,10 @@
<h3>Current Team</h3> <h3>Current Team</h3>
<p>Name: {{ session('currentTeam.name') }}</p> <p>Name: {{ session('currentTeam.name') }}</p>
<livewire:switch-team/> <livewire:switch-team/>
<div class="h-12"></div>
<h3>Notifications</h3>
<livewire:notifications.discord-settings :model="session('currentTeam')" />
<livewire:notifications.email-settings :model="session('currentTeam')" />
<div class="h-12"></div>
</div> </div>
</x-layout> </x-layout>

View File

@ -1,9 +1,7 @@
<?php <?php
use Illuminate\Foundation\Inspiring; use Illuminate\Foundation\Inspiring;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Symfony\Component\Mailer\Mailer;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -17,28 +15,5 @@
*/ */
Artisan::command('inspire', function () { Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
$smtp = [
"transport" => "smtp",
"host" => "mailpit",
"port" => 1025,
"encryption" => 'tls',
"username" => null,
"password" => null,
"timeout" => null,
"local_domain" => null,
];
config()->set('mail.mailers.smtp', $smtp);
// For testing custom SMTP Mailer
\Illuminate\Support\Facades\Mail::mailer('smtp')
->to('ask@me.com')
->send(new \App\Mail\ExampleMail);
// For sending a notification
// \Illuminate\Support\Facades\Notification::send(
// \App\Models\User::find(1),
// new \App\Notifications\TestMessage
// );
})->purpose('Display an inspiring quote'); })->purpose('Display an inspiring quote');