diff --git a/.gitignore b/.gitignore index d8bc3e6b2..e1b6b679e 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/app/Console/Commands/NotifyDemo.php b/app/Console/Commands/NotifyDemo.php new file mode 100644 index 000000000..1d5906bd0 --- /dev/null +++ b/app/Console/Commands/NotifyDemo.php @@ -0,0 +1,75 @@ +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' +
+
+ Coolify +
+

+ Demo Notify => Send a demo notification to a given channel. +

+

+ php artisan app:demo-notify {channel} +

+
+
Channels:
+ +
+
+ HTML); + + ask(<<<'HTML' +
+ In which manner you wish a coolified notification? +
+ HTML, ['email', 'slack', 'discord', 'telegram']); + } +} diff --git a/app/Http/Livewire/Notifications/DiscordSettings.php b/app/Http/Livewire/Notifications/DiscordSettings.php new file mode 100644 index 000000000..239d080a6 --- /dev/null +++ b/app/Http/Livewire/Notifications/DiscordSettings.php @@ -0,0 +1,43 @@ + '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'); + } +} diff --git a/app/Http/Livewire/Notifications/EmailSettings.php b/app/Http/Livewire/Notifications/EmailSettings.php new file mode 100644 index 000000000..26f955925 --- /dev/null +++ b/app/Http/Livewire/Notifications/EmailSettings.php @@ -0,0 +1,59 @@ + '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'); + } +} diff --git a/app/Jobs/SendMessageToDiscordJob.php b/app/Jobs/SendMessageToDiscordJob.php new file mode 100644 index 000000000..8f1962829 --- /dev/null +++ b/app/Jobs/SendMessageToDiscordJob.php @@ -0,0 +1,45 @@ + $this->text, + ]; + + Http::post($this->webhookUrl, $payload); + } +} diff --git a/app/Models/Team.php b/app/Models/Team.php index aaadcbb5a..fd5a0f06e 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -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); diff --git a/app/Notifications/Channels/CoolifyEmailChannel.php b/app/Notifications/Channels/CoolifyEmailChannel.php new file mode 100644 index 000000000..3f122070f --- /dev/null +++ b/app/Notifications/Channels/CoolifyEmailChannel.php @@ -0,0 +1,44 @@ +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, + ]); + } +} diff --git a/app/Notifications/Channels/DiscordChannel.php b/app/Notifications/Channels/DiscordChannel.php new file mode 100644 index 000000000..06c8c0130 --- /dev/null +++ b/app/Notifications/Channels/DiscordChannel.php @@ -0,0 +1,21 @@ +toDiscord($notifiable); + + $webhookUrl = $notifiable->routeNotificationForDiscord(); + + dispatch(new SendMessageToDiscordJob($message, $webhookUrl)); + } +} diff --git a/app/Notifications/Channels/SendsCoolifyEmail.php b/app/Notifications/Channels/SendsCoolifyEmail.php new file mode 100644 index 000000000..e1698a1fe --- /dev/null +++ b/app/Notifications/Channels/SendsCoolifyEmail.php @@ -0,0 +1,8 @@ + + */ + 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 + */ + public function toArray(object $notifiable): array + { + return [ + // + ]; + } +} diff --git a/database/migrations/2023_03_20_112811_create_teams_table.php b/database/migrations/2023_03_20_112811_create_teams_table.php index 77e7b840f..4396c8db7 100644 --- a/database/migrations/2023_03_20_112811_create_teams_table.php +++ b/database/migrations/2023_03_20_112811_create_teams_table.php @@ -16,6 +16,7 @@ return new class extends Migration $table->string('uuid')->unique(); $table->string('name'); $table->boolean('personal_team')->default(false); + $table->schemalessAttributes('extra_attributes'); $table->timestamps(); }); } diff --git a/database/migrations/2023_03_20_112813_create_instance_settings_table.php b/database/migrations/2023_03_20_112813_create_instance_settings_table.php index e8396b036..22c709fb8 100644 --- a/database/migrations/2023_03_20_112813_create_instance_settings_table.php +++ b/database/migrations/2023_03_20_112813_create_instance_settings_table.php @@ -21,8 +21,8 @@ return new class extends Migration $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(); }); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 475f4e7c0..07177d96f 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -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 diff --git a/resources/views/emails/example.blade.php b/resources/views/emails/example.blade.php new file mode 100644 index 000000000..bbfbe24b1 --- /dev/null +++ b/resources/views/emails/example.blade.php @@ -0,0 +1 @@ +Hello I am an example diff --git a/resources/views/livewire/notifications/discord-settings.blade.php b/resources/views/livewire/notifications/discord-settings.blade.php new file mode 100644 index 000000000..9a1980d6f --- /dev/null +++ b/resources/views/livewire/notifications/discord-settings.blade.php @@ -0,0 +1,23 @@ +
+
Discord
+
+
+
+ +
+
+ +
+
+ + Submit + + + Send test message + +
+
+
diff --git a/resources/views/livewire/notifications/email-settings.blade.php b/resources/views/livewire/notifications/email-settings.blade.php new file mode 100644 index 000000000..dbcf16c3b --- /dev/null +++ b/resources/views/livewire/notifications/email-settings.blade.php @@ -0,0 +1,45 @@ +
+
E-mail - SMTP
+
+
+
+ +
+
+
+ +
+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+ + Submit + + + Send test message + +
+
+
diff --git a/resources/views/settings.blade.php b/resources/views/settings.blade.php index 761a5417f..389f8be41 100644 --- a/resources/views/settings.blade.php +++ b/resources/views/settings.blade.php @@ -1,3 +1,4 @@ +

Settings

diff --git a/resources/views/team.blade.php b/resources/views/team.blade.php index 01c89f164..d0de056dc 100644 --- a/resources/views/team.blade.php +++ b/resources/views/team.blade.php @@ -1,8 +1,13 @@ -

Teams

-
-
Currently Active Team:
-
{{ session('currentTeam')->name }}
+
+

Current Team

+

Name: {{ session('currentTeam.name') }}

+ +
+

Notifications

+ + +
diff --git a/routes/console.php b/routes/console.php index d2977bec5..e05f4c9a1 100644 --- a/routes/console.php +++ b/routes/console.php @@ -14,19 +14,6 @@ use Illuminate\Support\Facades\Artisan; | */ -// 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');