feat: add discord notifications

This commit is contained in:
Andras Bacsai 2023-09-06 14:31:38 +02:00
parent df1b9e7319
commit f9a2ff6d90
21 changed files with 419 additions and 123 deletions

View File

@ -8,26 +8,30 @@
class DiscordSettings extends Component
{
public Team $model;
public Team $team;
protected $rules = [
'model.discord_enabled' => 'nullable|boolean',
'model.discord_webhook_url' => 'required|url',
'model.discord_notifications_test' => 'nullable|boolean',
'model.discord_notifications_deployments' => 'nullable|boolean',
'model.discord_notifications_status_changes' => 'nullable|boolean',
'model.discord_notifications_database_backups' => 'nullable|boolean',
'team.discord_enabled' => 'nullable|boolean',
'team.discord_webhook_url' => 'required|url',
'team.discord_notifications_test' => 'nullable|boolean',
'team.discord_notifications_deployments' => 'nullable|boolean',
'team.discord_notifications_status_changes' => 'nullable|boolean',
'team.discord_notifications_database_backups' => 'nullable|boolean',
];
protected $validationAttributes = [
'model.discord_webhook_url' => 'Discord Webhook',
'team.discord_webhook_url' => 'Discord Webhook',
];
public function mount()
{
$this->team = auth()->user()->currentTeam();
}
public function instantSave()
{
try {
$this->submit();
} catch (\Exception $e) {
ray($e->getMessage());
$this->model->discord_enabled = false;
$this->team->discord_enabled = false;
$this->validate();
}
}
@ -41,8 +45,8 @@ public function submit()
public function saveModel()
{
$this->model->save();
if (is_a($this->model, Team::class)) {
$this->team->save();
if (is_a($this->team, Team::class)) {
refreshSession();
}
$this->emit('success', 'Settings saved.');
@ -50,7 +54,7 @@ public function saveModel()
public function sendTestNotification()
{
$this->model->notify(new Test);
$this->team->notify(new Test());
$this->emit('success', 'Test notification sent.');
}
}

View File

@ -49,6 +49,7 @@ class EmailSettings extends Component
public function mount()
{
$this->team = auth()->user()->currentTeam();
['sharedEmailEnabled' => $this->sharedEmailEnabled] = $this->team->limits;
$this->emails = auth()->user()->email;
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Http\Livewire\Notifications;
use App\Models\Team;
use App\Notifications\Test;
use Livewire\Component;
class TelegramSettings extends Component
{
public Team $team;
protected $rules = [
'team.telegram_enabled' => 'nullable|boolean',
'team.telegram_token' => 'required|string',
'team.telegram_chat_id' => 'required|string',
'team.telegram_notifications_test' => 'nullable|boolean',
'team.telegram_notifications_deployments' => 'nullable|boolean',
'team.telegram_notifications_status_changes' => 'nullable|boolean',
'team.telegram_notifications_database_backups' => 'nullable|boolean',
];
protected $validationAttributes = [
'team.telegram_token' => 'Token',
'team.telegram_chat_id' => 'Chat ID',
];
public function mount()
{
$this->team = auth()->user()->currentTeam();
}
public function instantSave()
{
try {
$this->submit();
} catch (\Exception $e) {
ray($e->getMessage());
$this->team->telegram_enabled = false;
$this->validate();
}
}
public function submit()
{
$this->resetErrorBag();
$this->validate();
$this->saveModel();
}
public function saveModel()
{
$this->team->save();
if (is_a($this->team, Team::class)) {
refreshSession();
}
$this->emit('success', 'Settings saved.');
}
public function sendTestNotification()
{
$this->team->notify(new Test());
$this->emit('success', 'Test notification sent.');
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Str;
class SendMessageToTelegramJob 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 array $buttons,
public string $token,
public string $chatId,
) {
}
/**
* Execute the job.
*/
public function handle(): void
{
$url = 'https://api.telegram.org/bot' . $this->token . '/sendMessage';
$inlineButtons = [];
if (!empty($this->buttons)) {
foreach ($this->buttons as $button) {
$buttonUrl = data_get($button, 'url');
if ($buttonUrl && Str::contains($buttonUrl, 'http://localhost')) {
$buttonUrl = str_replace('http://localhost', config('app.url'), $buttonUrl);
}
$inlineButtons[] = [
'text' => $button['text'],
'url' => $buttonUrl,
];
}
}
$payload = [
'parse_mode' => 'markdown',
'reply_markup' => json_encode([
'inline_keyboard' => [
[...$inlineButtons],
],
]),
'chat_id' => $this->chatId,
'text' => $this->text,
];
ray($payload);
$response = Http::post($url, $payload);
if ($response->failed()) {
throw new \Exception('Telegram notification failed with ' . $response->status() . ' status code.' . $response->body());
}
}
}

View File

@ -24,6 +24,14 @@ public function routeNotificationForDiscord()
return data_get($this, 'discord_webhook_url', null);
}
public function routeNotificationForTelegram()
{
return [
"token" => data_get($this, 'telegram_token', null),
"chat_id" => data_get($this, 'telegram_chat_id', null)
];
}
public function getRecepients($notification)
{
$recipients = data_get($notification, 'emails', null);

View File

@ -43,19 +43,7 @@ public function __construct(Application $application, string $deployment_uuid, ?
public function via(object $notifiable): array
{
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_deployments');
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_deployments');
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
$channels[] = EmailChannel::class;
}
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
$channels[] = DiscordChannel::class;
}
return $channels;
return setNotificationChannels($notifiable, 'deployments');
}
public function toMail(): MailMessage
@ -89,4 +77,19 @@ public function toDiscord(): string
}
return $message;
}
public function toTelegram(): array
{
if ($this->preview) {
$message = '❌ Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
} else {
$message = '❌ Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
}
return [
"message" => $message,
"buttons" => [
"text" => "View Deployment Logs",
"url" => $this->deployment_url
],
];
}
}

View File

@ -43,19 +43,7 @@ public function __construct(Application $application, string $deployment_uuid, A
public function via(object $notifiable): array
{
$channels = [];
$isEmailEnabled = data_get($notifiable, 'smtp_enabled');
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_deployments');
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_deployments');
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
$channels[] = EmailChannel::class;
}
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
$channels[] = DiscordChannel::class;
}
return $channels;
return setNotificationChannels($notifiable, 'deployments');
}
public function toMail(): MailMessage
@ -99,4 +87,34 @@ public function toDiscord(): string
}
return $message;
}
public function toTelegram(): array
{
if ($this->preview) {
$message = '✅ New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '';
if ($this->preview->fqdn) {
$buttons[] = [
"text" => "Open Application",
"url" => $this->preview->fqdn
];
}
} else {
$message = '✅ New version successfully deployed of ' . $this->application_name . '';
if ($this->fqdn) {
$buttons[] = [
"text" => "Open Application",
"url" => $this->fqdn
];
}
}
$buttons[] = [
"text" => "Deployment logs",
"url" => $this->deployment_url
];
return [
"message" => $message,
"buttons" => [
...$buttons
],
];
}
}

View File

@ -37,19 +37,7 @@ public function __construct($application)
public function via(object $notifiable): array
{
$channels = [];
$isEmailEnabled = data_get($notifiable, 'smtp_enabled');
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_status_changes');
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_status_changes');
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
$channels[] = EmailChannel::class;
}
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
$channels[] = DiscordChannel::class;
}
return $channels;
return setNotificationChannels($notifiable, 'status_changes');
}
public function toMail(): MailMessage
@ -70,7 +58,20 @@ public function toDiscord(): string
$message = '⛔ ' . $this->application_name . ' has been stopped.
';
$message .= '[Application URL](' . $this->application_url . ')';
$message .= '[Open Application in Coolify](' . $this->application_url . ')';
return $message;
}
public function toTelegram(): array
{
$message = '⛔ ' . $this->application_name . ' has been stopped.';
return [
"message" => $message,
"buttons" => [
[
"text" => "Open Application in Coolify",
"url" => $this->application_url
]
],
];
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Notifications\Channels;
interface SendsTelegram
{
public function routeNotificationForTelegram();
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Notifications\Channels;
use App\Jobs\SendMessageToTelegramJob;
class TelegramChannel
{
public function send($notifiable, $notification): void
{
$data = $notification->toTelegram($notifiable);
$telegramData = $notifiable->routeNotificationForTelegram();
$message = data_get($data, 'message');
$buttons = data_get($data, 'buttons', []);
ray($message, $buttons);
$telegramToken = data_get($telegramData, 'token');
$chatId = data_get($telegramData, 'chat_id');
if (!$telegramToken || !$chatId || !$message) {
throw new \Exception('Telegram token, chat id and message are required');
}
dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId));
}
}

View File

@ -25,19 +25,7 @@ public function __construct(ScheduledDatabaseBackup $backup, public $database, p
public function via(object $notifiable): array
{
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups');
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups');
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
$channels[] = EmailChannel::class;
}
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
$channels[] = DiscordChannel::class;
}
return $channels;
return setNotificationChannels($notifiable, 'database_backups');
}
public function toMail(): MailMessage
@ -56,4 +44,11 @@ public function toDiscord(): string
{
return "❌ Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
}
public function toTelegram(): array
{
$message = "❌ Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
return [
"message" => $message,
];
}
}

View File

@ -25,19 +25,7 @@ public function __construct(ScheduledDatabaseBackup $backup, public $database)
public function via(object $notifiable): array
{
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups');
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups');
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
$channels[] = EmailChannel::class;
}
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
$channels[] = DiscordChannel::class;
}
return $channels;
return setNotificationChannels($notifiable, 'database_backups');
}
public function toMail(): MailMessage
@ -55,4 +43,11 @@ public function toDiscord(): string
{
return "✅ Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
}
public function toTelegram(): array
{
$message = "✅ Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
return [
"message" => $message,
];
}
}

View File

@ -3,6 +3,7 @@
namespace App\Notifications\Internal;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\TelegramChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
@ -17,11 +18,17 @@ public function __construct(public string $message)
public function via(object $notifiable): array
{
return [DiscordChannel::class];
return [TelegramChannel::class, DiscordChannel::class];
}
public function toDiscord(): string
{
return $this->message;
}
public function toTelegram(): array
{
return [
"message" => $this->message,
];
}
}

View File

@ -22,19 +22,7 @@ public function __construct(public Server $server)
public function via(object $notifiable): array
{
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_status_changes');
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_status_changes');
// if ($isEmailEnabled && $isSubscribedToEmailEvent) {
// $channels[] = EmailChannel::class;
// }
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
$channels[] = DiscordChannel::class;
}
return $channels;
return setNotificationChannels($notifiable, 'status_changes');
}
public function toMail(): MailMessage
@ -55,4 +43,10 @@ public function toDiscord(): string
$message = '⛔ Server \'' . $this->server->name . '\' is unreachable (could be a temporary issue). If you receive this more than twice in a row, please check your server.';
return $message;
}
public function toTelegram(): array
{
return [
"message" => '⛔ Server \'' . $this->server->name . '\' is unreachable (could be a temporary issue). If you receive this more than twice in a row, please check your server.'
];
}
}

View File

@ -4,6 +4,7 @@
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@ -19,18 +20,7 @@ public function __construct(public string|null $emails = null)
public function via(object $notifiable): array
{
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
if ($isDiscordEnabled && empty($this->emails)) {
$channels[] = DiscordChannel::class;
}
if ($isEmailEnabled && !empty($this->emails)) {
$channels[] = EmailChannel::class;
}
return $channels;
return setNotificationChannels($notifiable, 'test');
}
public function toMail(): MailMessage
@ -48,4 +38,16 @@ public function toDiscord(): string
$message .= '[Go to your dashboard](' . base_url() . ')';
return $message;
}
public function toTelegram(): array
{
return [
"message" => 'This is a test Telegram notification from Coolify.',
"buttons" => [
[
"text" => "Go to your dashboard",
"url" => 'https://coolify.io'
]
],
];
}
}

View File

@ -2,6 +2,9 @@
use App\Models\InstanceSettings;
use App\Models\Team;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use App\Notifications\Internal\GeneralNotification;
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Illuminate\Database\QueryException;
@ -242,10 +245,9 @@ function validate_cron_expression($expression_to_validate): bool
function send_internal_notification(string $message): void
{
try {
ray('Sending internal notification... 📬 ' . $message);
$baseUrl = config('app.name');
$team = Team::find(0);
$team->notify(new GeneralNotification("👀 Internal notifications from {$baseUrl}: " . $message));
$team->notify(new GeneralNotification("👀 {$baseUrl}: " . $message));
} catch (\Throwable $th) {
ray($th->getMessage());
}
@ -270,3 +272,23 @@ function isEmailEnabled($notifiable)
{
return data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') || data_get($notifiable, 'use_instance_email_settings');
}
function setNotificationChannels($notifiable, $event)
{
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
$isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event");
$isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event");
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
$channels[] = DiscordChannel::class;
}
if ($isEmailEnabled) {
$channels[] = EmailChannel::class;
}
if ($isTelegramEnabled && $isSubscribedToTelegramEvent) {
$channels[] = TelegramChannel::class;
}
return $channels;
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('teams', function (Blueprint $table) {
$table->boolean('telegram_enabled')->default(false);
$table->text('telegram_token')->nullable();
$table->text('telegram_chat_id')->nullable();
$table->boolean('telegram_notifications_test')->default(true);
$table->boolean('telegram_notifications_deployments')->default(true);
$table->boolean('telegram_notifications_status_changes')->default(true);
$table->boolean('telegram_notifications_database_backups')->default(true);
});
}
public function down(): void
{
Schema::table('teams', function (Blueprint $table) {
$table->dropColumn('telegram_enabled');
$table->dropColumn('telegram_token');
$table->dropColumn('telegram_chat_id');
$table->dropColumn('telegram_notifications_test');
$table->dropColumn('telegram_notifications_deployments');
$table->dropColumn('telegram_notifications_status_changes');
$table->dropColumn('telegram_notifications_database_backups');
});
}
};

View File

@ -5,8 +5,8 @@
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<div class="absolute hidden text-xs rounded group-hover:block border-coolgray-400 bg-coolgray-500">
<div class="p-4 card-body">
<div class="absolute z-40 hidden text-xs rounded group-hover:block border-coolgray-400 bg-coolgray-500">
<div class="p-4">
{!! $helper !!}
</div>
</div>

View File

@ -5,7 +5,7 @@
<x-forms.button type="submit">
Save
</x-forms.button>
@if ($model->discord_enabled)
@if ($team->discord_enabled)
<x-forms.button class="text-white normal-case btn btn-xs no-animation btn-primary"
wire:click="sendTestNotification">
Send Test Notifications
@ -13,26 +13,26 @@
@endif
</div>
<div class="w-48">
<x-forms.checkbox instantSave id="model.discord_enabled" label="Notification Enabled" />
<x-forms.checkbox instantSave id="team.discord_enabled" label="Notification Enabled" />
</div>
<x-forms.input type="password"
helper="Generate a webhook in Discord.<br>Example: https://discord.com/api/webhooks/...." required
id="model.discord_webhook_url" label="Webhook" />
id="team.discord_webhook_url" label="Webhook" />
</form>
@if (data_get($model, 'discord_enabled'))
@if (data_get($team, 'discord_enabled'))
<h3 class="mt-4">Subscribe to events</h3>
<div class="w-64">
@if (isDev())
<x-forms.checkbox instantSave="saveModel" id="model.discord_notifications_test" label="Test" />
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_test" label="Test" />
@endif
<h4 class="mt-4">General</h4>
<x-forms.checkbox instantSave="saveModel" id="model.discord_notifications_status_changes"
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_status_changes"
label="Container Status Changes" />
<h4 class="mt-4">Applications</h4>
<x-forms.checkbox instantSave="saveModel" id="model.discord_notifications_deployments"
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_deployments"
label="Deployments" />
<h4 class="mt-4">Databases</h4>
<x-forms.checkbox instantSave="saveModel" id="model.discord_notifications_database_backups"
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_database_backups"
label="Backup Statuses" />
</div>
@endif

View File

@ -0,0 +1,42 @@
<div>
<form wire:submit.prevent='submit' class="flex flex-col">
<div class="flex items-center gap-2">
<h2>Telegram</h2>
<x-forms.button type="submit">
Save
</x-forms.button>
@if ($team->telegram_enabled)
<x-forms.button class="text-white normal-case btn btn-xs no-animation btn-primary"
wire:click="sendTestNotification">
Send Test Notifications
</x-forms.button>
@endif
</div>
<div class="w-48">
<x-forms.checkbox instantSave id="team.telegram_enabled" label="Notification Enabled" />
</div>
<div class="flex gap-2">
<x-forms.input type="password" helper="Get it from the <a class='inline-block text-white underline' href='https://t.me/botfather' target='_blank'>BotFather Bot</a> on Telegram." required
id="team.telegram_token" label="Token" />
<x-forms.input type="password" helper="Recommended to add your bot to a group chat and add its Chat ID here." required
id="team.telegram_chat_id" label="Chat ID" />
</div>
</form>
@if (data_get($team, 'telegram_enabled'))
<h3 class="mt-4">Subscribe to events</h3>
<div class="w-64">
@if (isDev())
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_test" label="Test" />
@endif
<h4 class="mt-4">General</h4>
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_status_changes"
label="Container Status Changes" />
<h4 class="mt-4">Applications</h4>
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_deployments"
label="Deployments" />
<h4 class="mt-4">Databases</h4>
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_database_backups"
label="Backup Statuses" />
</div>
@endif
</div>

View File

@ -1,23 +1,25 @@
<x-layout>
<x-team.navbar/>
<x-team.navbar />
<h2 class="pb-4">Notifications</h2>
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'email' }" class="flex h-full">
<div class="flex flex-col gap-4 min-w-fit">
<a :class="activeTab === 'email' && 'text-white'"
@click.prevent="activeTab = 'email'; window.location.hash = 'email'" href="#">Email</a>
<a :class="activeTab === 'Telegram' && 'text-white'"
@click.prevent="activeTab = 'telegram'; window.location.hash = 'telegram'" href="#">Telegram</a>
<a :class="activeTab === 'discord' && 'text-white'"
@click.prevent="activeTab = 'discord'; window.location.hash = 'discord'" href="#">Discord</a>
</div>
<div class="w-full pl-8">
<div x-cloak x-show="activeTab === 'email'" class="h-full">
<livewire:notifications.email-settings :team="auth()
->user()
->currentTeam()" />
<livewire:notifications.email-settings />
</div>
<div x-cloak x-show="activeTab === 'telegram'" class="h-full">
<livewire:notifications.telegram-settings />
</div>
<div x-cloak x-show="activeTab === 'discord'">
<livewire:notifications.discord-settings :model="auth()
->user()
->currentTeam()" />
<livewire:notifications.discord-settings />
</div>
</div>
</x-layout>