Merge pull request #1073 from coollabsio/notifications
Starts Notifications feature. Missing to send email with runtime configs
This commit is contained in:
commit
c8f70a4e3b
6
.gitignore
vendored
6
.gitignore
vendored
@ -20,10 +20,8 @@ yarn-error.log
|
||||
/.npm
|
||||
/.bash_history
|
||||
/_data
|
||||
|
||||
# Temp while developing Proxy deployment
|
||||
resources/recipes
|
||||
|
||||
_testing_hosts/
|
||||
_volumes/
|
||||
.lesshst
|
||||
psysh_history
|
||||
.psql_history
|
||||
|
75
app/Console/Commands/NotifyDemo.php
Normal file
75
app/Console/Commands/NotifyDemo.php
Normal 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']);
|
||||
}
|
||||
}
|
43
app/Http/Livewire/Notifications/DiscordSettings.php
Normal file
43
app/Http/Livewire/Notifications/DiscordSettings.php
Normal 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 sendTestNotification()
|
||||
{
|
||||
Notification::send($this->model, new DemoNotification);
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.notifications.discord-settings');
|
||||
}
|
||||
}
|
59
app/Http/Livewire/Notifications/EmailSettings.php
Normal file
59
app/Http/Livewire/Notifications/EmailSettings.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?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 EmailSettings extends Component
|
||||
{
|
||||
public Team|Server $model;
|
||||
|
||||
protected $rules = [
|
||||
'model.extra_attributes.smtp_active' => 'nullable|boolean',
|
||||
'model.extra_attributes.from_address' => 'nullable',
|
||||
'model.extra_attributes.from_name' => 'nullable',
|
||||
'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.from_address' => 'From Address',
|
||||
'model.extra_attributes.from_name' => 'From Name',
|
||||
'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()
|
||||
{
|
||||
Notification::send($this->model, new DemoNotification);
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.notifications.email-settings');
|
||||
}
|
||||
}
|
45
app/Jobs/SendMessageToDiscordJob.php
Normal file
45
app/Jobs/SendMessageToDiscordJob.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class SendMessageToDiscordJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*/
|
||||
public int $maxExceptions = 3;
|
||||
|
||||
public function __construct(
|
||||
public string $text,
|
||||
public string $webhookUrl
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$payload = [
|
||||
'content' => $this->text,
|
||||
];
|
||||
|
||||
Http::post($this->webhookUrl, $payload);
|
||||
}
|
||||
}
|
@ -2,28 +2,59 @@
|
||||
|
||||
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 = [
|
||||
'extra_attributes' => SchemalessAttributes::class,
|
||||
'personal_team' => 'boolean',
|
||||
];
|
||||
protected $fillable = [
|
||||
'id',
|
||||
'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()
|
||||
{
|
||||
return $this->hasMany(Project::class);
|
||||
}
|
||||
|
||||
public function servers()
|
||||
{
|
||||
return $this->hasMany(Server::class);
|
||||
}
|
||||
|
||||
public function applications()
|
||||
{
|
||||
return $this->hasManyThrough(Application::class, Project::class);
|
||||
}
|
||||
|
||||
public function privateKeys()
|
||||
{
|
||||
return $this->hasMany(PrivateKey::class);
|
||||
|
44
app/Notifications/Channels/CoolifyEmailChannel.php
Normal file
44
app/Notifications/Channels/CoolifyEmailChannel.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?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(
|
||||
$notifiable->extra_attributes?->get('from_address'),
|
||||
$notifiable->extra_attributes?->get('from_name')
|
||||
)
|
||||
->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,
|
||||
]);
|
||||
}
|
||||
}
|
21
app/Notifications/Channels/DiscordChannel.php
Normal file
21
app/Notifications/Channels/DiscordChannel.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Channels;
|
||||
|
||||
use App\Jobs\SendMessageToDiscordJob;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class DiscordChannel
|
||||
{
|
||||
/**
|
||||
* Send the given notification.
|
||||
*/
|
||||
public function send(SendsDiscord $notifiable, Notification $notification): void
|
||||
{
|
||||
$message = $notification->toDiscord($notifiable);
|
||||
|
||||
$webhookUrl = $notifiable->routeNotificationForDiscord();
|
||||
|
||||
dispatch(new SendMessageToDiscordJob($message, $webhookUrl));
|
||||
}
|
||||
}
|
8
app/Notifications/Channels/SendsCoolifyEmail.php
Normal file
8
app/Notifications/Channels/SendsCoolifyEmail.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Channels;
|
||||
|
||||
interface SendsCoolifyEmail
|
||||
{
|
||||
public function routeNotificationForCoolifyEmail();
|
||||
}
|
8
app/Notifications/Channels/SendsDiscord.php
Normal file
8
app/Notifications/Channels/SendsDiscord.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Channels;
|
||||
|
||||
interface SendsDiscord
|
||||
{
|
||||
public function routeNotificationForDiscord();
|
||||
}
|
65
app/Notifications/DemoNotification.php
Normal file
65
app/Notifications/DemoNotification.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Notifications\Channels\CoolifyEmailChannel;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class DemoNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
$channels = [];
|
||||
$notifiable->extra_attributes?->get('smtp_active') && $channels[] = CoolifyEmailChannel::class;
|
||||
$notifiable->extra_attributes?->get('discord_active') && $channels[] = DiscordChannel::class;
|
||||
return $channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*/
|
||||
public function toMail(object $notifiable): MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject('Coolify demo notification')
|
||||
->line('Welcome to Coolify!')
|
||||
->action('Go to dashboard', url('/'))
|
||||
->line('We need your attention for disk usage.');
|
||||
}
|
||||
|
||||
public function toDiscord(object $notifiable): string
|
||||
{
|
||||
return 'Welcome to Coolify! We need your attention for disk usage. [Go to dashboard]('.url('/').')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ public function up(): void
|
||||
$table->string('uuid')->unique();
|
||||
$table->string('name');
|
||||
$table->boolean('personal_team')->default(false);
|
||||
$table->schemalessAttributes('extra_attributes');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ public function up(): void
|
||||
$table->boolean('do_not_track')->default(false);
|
||||
$table->boolean('is_auto_update_enabled')->default(true);
|
||||
$table->boolean('is_registration_enabled')->default(true);
|
||||
// $table->string('preview_domain_separator')->default('.');
|
||||
// $table->string('custom_dns_servers')->default('1.1.1.1,8.8.8.8');
|
||||
// $table->string('preview_domain_separator')->default('.');
|
||||
// $table->boolean('is_dns_check_enabled')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
@ -62,6 +62,11 @@ services:
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- "./_data/coolify/proxy/testing-host-2:/data/coolify/proxy"
|
||||
mailpit:
|
||||
image: 'axllent/mailpit:latest'
|
||||
ports:
|
||||
- '${FORWARD_MAILPIT_PORT:-1025}:1025'
|
||||
- '${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025'
|
||||
buggregator:
|
||||
image: ghcr.io/buggregator/server:latest
|
||||
container_name: coolify-debug
|
||||
|
1
resources/views/emails/example.blade.php
Normal file
1
resources/views/emails/example.blade.php
Normal file
@ -0,0 +1 @@
|
||||
Hello I am an example
|
@ -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="sendTestNotification"
|
||||
>
|
||||
Send test message
|
||||
</x-inputs.button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -0,0 +1,45 @@
|
||||
<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.from_address" label="From Address" />
|
||||
<x-inputs.input id="model.extra_attributes.from_name" label="From Name" />
|
||||
<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>
|
@ -1,3 +1,4 @@
|
||||
<x-layout>
|
||||
<h1>Settings</h1>
|
||||
<livewire:settings.form :settings="$settings" />
|
||||
</x-layout>
|
||||
|
@ -1,8 +1,13 @@
|
||||
<x-layout>
|
||||
<h1>Teams</h1>
|
||||
<div class="flex gap-2">
|
||||
<div>Currently Active Team:</div>
|
||||
<div class='text-white'>{{ session('currentTeam')->name }}</div>
|
||||
<div>
|
||||
<h3>Current Team</h3>
|
||||
<p>Name: {{ session('currentTeam.name') }}</p>
|
||||
<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>
|
||||
<livewire:switch-team>
|
||||
</x-layout>
|
||||
|
@ -14,19 +14,6 @@
|
||||
|
|
||||
*/
|
||||
|
||||
// Artisan::command('inspire', function () {
|
||||
|
||||
// $activity = Spatie\Activitylog\Models\Activity::latest()->first();
|
||||
|
||||
// $this->info(
|
||||
// collect(
|
||||
// json_decode(data_get($activity, 'description'), associative: true, flags: JSON_THROW_ON_ERROR)
|
||||
// )
|
||||
// ->sortBy('order')
|
||||
// ->map(fn($i) => $i['output'])
|
||||
// ->implode("\n")
|
||||
// );
|
||||
|
||||
|
||||
|
||||
// })->purpose('Display an inspiring quote');
|
||||
Artisan::command('inspire', function () {
|
||||
$this->comment(Inspiring::quote());
|
||||
})->purpose('Display an inspiring quote');
|
||||
|
Loading…
Reference in New Issue
Block a user