Add database notifications (#817)

* add database notifications to all panels

* add successful param to Installed event

* add listener for Installed event

* create event for subuser creation

* add listener for SubUserAdded event

* always send Installed event

* create event for subuser removal

* add listener for SubUserRemoved event

* add prefix to server name

* remove view action from SubUserRemoved notification
This commit is contained in:
Boy132 2024-12-12 14:38:45 +01:00 committed by GitHub
parent eb819032bc
commit d09227659e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 176 additions and 45 deletions

View File

@ -13,5 +13,5 @@ class Installed extends Event
/** /**
* Create a new event instance. * Create a new event instance.
*/ */
public function __construct(public Server $server) {} public function __construct(public Server $server, public bool $successful, public bool $initialInstall) {}
} }

View File

@ -0,0 +1,17 @@
<?php
namespace App\Events\Server;
use App\Events\Event;
use App\Models\Subuser;
use Illuminate\Queue\SerializesModels;
class SubUserAdded extends Event
{
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(public Subuser $subuser) {}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Events\Server;
use App\Events\Event;
use App\Models\Server;
use App\Models\User;
use Illuminate\Queue\SerializesModels;
class SubUserRemoved extends Event
{
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(public Server $server, public User $user) {}
}

View File

@ -35,13 +35,11 @@ class ServerInstallController extends Controller
{ {
$status = null; $status = null;
// Make sure the type of failure is accurate $successful = $request->boolean('successful');
if (!$request->boolean('successful')) {
$status = ServerState::InstallFailed;
if ($request->boolean('reinstall')) { // Make sure the type of failure is accurate
$status = ServerState::ReinstallFailed; if (!$successful) {
} $status = $request->boolean('reinstall') ? ServerState::ReinstallFailed : ServerState::InstallFailed;
} }
// Keep the server suspended if it's already suspended // Keep the server suspended if it's already suspended
@ -55,16 +53,8 @@ class ServerInstallController extends Controller
$server->installed_at = now(); $server->installed_at = now();
$server->save(); $server->save();
// If the server successfully installed, fire installed event.
// This logic allows individually disabling install and reinstall notifications separately.
$isInitialInstall = is_null($previouslyInstalledAt); $isInitialInstall = is_null($previouslyInstalledAt);
if ($isInitialInstall && config()->get('panel.email.send_install_notification', true)) { event(new ServerInstalled($server, $successful, $isInitialInstall));
event(new ServerInstalled($server));
}
if (!$isInitialInstall && config()->get('panel.email.send_reinstall_notification', true)) {
event(new ServerInstalled($server));
}
return new JsonResponse([], Response::HTTP_NO_CONTENT); return new JsonResponse([], Response::HTTP_NO_CONTENT);
} }

View File

@ -0,0 +1,29 @@
<?php
namespace App\Listeners\Server;
use App\Events\Server\Installed;
use App\Filament\Server\Pages\Console;
use Filament\Notifications\Actions\Action;
use Filament\Notifications\Notification;
class ServerInstalledListener
{
public function handle(Installed $event): void
{
$event->server->loadMissing('user');
Notification::make()
->status($event->successful ? 'success' : 'danger')
->title('Server ' . ($event->initialInstall ? 'Installation' : 'Reinstallation') . ' ' . ($event->successful ? 'completed' : 'failed'))
->body('Server Name: ' . $event->server->name)
->actions([
Action::make('view')
->button()
->label('Open Server')
->markAsRead()
->url(fn () => Console::getUrl(panel: 'server', tenant: $event->server)),
])
->sendToDatabase($event->server->user);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Listeners\Server;
use App\Events\Server\SubUserAdded;
use App\Filament\Server\Pages\Console;
use Filament\Notifications\Actions\Action;
use Filament\Notifications\Notification;
class SubUserAddedListener
{
public function handle(SubUserAdded $event): void
{
$event->subuser->loadMissing('server');
$event->subuser->loadMissing('user');
Notification::make()
->title('Added to Server')
->body('You have been added as a subuser to ' . $event->subuser->server->name . '.')
->actions([
Action::make('view')
->button()
->label('Open Server')
->markAsRead()
->url(fn () => Console::getUrl(panel: 'server', tenant: $event->subuser->server)),
])
->sendToDatabase($event->subuser->user);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Listeners\Server;
use App\Events\Server\SubUserRemoved;
use Filament\Notifications\Notification;
class SubUserRemovedListener
{
public function handle(SubUserRemoved $event): void
{
Notification::make()
->title('Removed from Server')
->body('You have been removed as a subuser from ' . $event->server->name . '.')
->sendToDatabase($event->user);
}
}

View File

@ -2,7 +2,12 @@
namespace App\Notifications; namespace App\Notifications;
use App\Events\Server\SubUserAdded;
use App\Models\Server;
use App\Models\User;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Container\Container;
use Illuminate\Contracts\Notifications\Dispatcher;
use Illuminate\Notifications\Notification; use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@ -11,14 +16,22 @@ class AddedToServer extends Notification implements ShouldQueue
{ {
use Queueable; use Queueable;
public object $server; public Server $server;
public User $user;
/** /**
* Create a new notification instance. * Handle a direct call to this notification from the subuser added event. This is configured
* in the event service provider.
*/ */
public function __construct(array $server) public function handle(SubUserAdded $event): void
{ {
$this->server = (object) $server; $this->server = $event->subuser->server;
$this->user = $event->subuser->user;
// Since we are calling this notification directly from an event listener we need to fire off the dispatcher
// to send the email now. Don't use send() or you'll end up firing off two different events.
Container::getInstance()->make(Dispatcher::class)->sendNow($this->user, $this);
} }
/** /**
@ -35,7 +48,7 @@ class AddedToServer extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
return (new MailMessage()) return (new MailMessage())
->greeting('Hello ' . $this->server->user . '!') ->greeting('Hello ' . $this->user->username . '!')
->line('You have been added as a subuser for the following server, allowing you certain control over the server.') ->line('You have been added as a subuser for the following server, allowing you certain control over the server.')
->line('Server Name: ' . $this->server->name) ->line('Server Name: ' . $this->server->name)
->action('Visit Server', url('/server/' . $this->server->uuid_short)); ->action('Visit Server', url('/server/' . $this->server->uuid_short));

View File

@ -2,7 +2,12 @@
namespace App\Notifications; namespace App\Notifications;
use App\Events\Server\SubUserRemoved;
use App\Models\Server;
use App\Models\User;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Container\Container;
use Illuminate\Contracts\Notifications\Dispatcher;
use Illuminate\Notifications\Notification; use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@ -11,14 +16,22 @@ class RemovedFromServer extends Notification implements ShouldQueue
{ {
use Queueable; use Queueable;
public object $server; public Server $server;
public User $user;
/** /**
* Create a new notification instance. * Handle a direct call to this notification from the subuser removed event. This is configured
* in the event service provider.
*/ */
public function __construct(array $server) public function handle(SubUserRemoved $event): void
{ {
$this->server = (object) $server; $this->server = $event->server;
$this->user = $event->user;
// Since we are calling this notification directly from an event listener we need to fire off the dispatcher
// to send the email now. Don't use send() or you'll end up firing off two different events.
Container::getInstance()->make(Dispatcher::class)->sendNow($this->user, $this);
} }
/** /**
@ -36,7 +49,7 @@ class RemovedFromServer extends Notification implements ShouldQueue
{ {
return (new MailMessage()) return (new MailMessage())
->error() ->error()
->greeting('Hello ' . $this->server->user . '.') ->greeting('Hello ' . $this->user->username . '.')
->line('You have been removed as a subuser for the following server.') ->line('You have been removed as a subuser for the following server.')
->line('Server Name: ' . $this->server->name) ->line('Server Name: ' . $this->server->name)
->action('Visit Panel', route('index')); ->action('Visit Panel', route('index'));

View File

@ -4,7 +4,6 @@ namespace App\Notifications;
use App\Models\User; use App\Models\User;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use App\Events\Event;
use App\Models\Server; use App\Models\Server;
use Illuminate\Container\Container; use Illuminate\Container\Container;
use App\Events\Server\Installed; use App\Events\Server\Installed;
@ -27,14 +26,24 @@ class ServerInstalled extends Notification implements ShouldQueue
*/ */
public function handle(Installed $event): void public function handle(Installed $event): void
{ {
$event->server->loadMissing('user'); if ($event->initialInstall && !config()->get('panel.email.send_install_notification', true)) {
return;
}
$this->server = $event->server; if (!$event->initialInstall && !config()->get('panel.email.send_reinstall_notification', true)) {
$this->user = $event->server->user; return;
}
// Since we are calling this notification directly from an event listener we need to fire off the dispatcher if ($event->successful) {
// to send the email now. Don't use send() or you'll end up firing off two different events. $event->server->loadMissing('user');
Container::getInstance()->make(Dispatcher::class)->sendNow($this->user, $this);
$this->server = $event->server;
$this->user = $event->server->user;
// Since we are calling this notification directly from an event listener we need to fire off the dispatcher
// to send the email now. Don't use send() or you'll end up firing off two different events.
Container::getInstance()->make(Dispatcher::class)->sendNow($this->user, $this);
}
} }
/** /**

View File

@ -55,6 +55,7 @@ class AdminPanelProvider extends PanelProvider
->spa() ->spa()
->discoverResources(in: app_path('Filament/Admin/Resources'), for: 'App\\Filament\\Admin\\Resources') ->discoverResources(in: app_path('Filament/Admin/Resources'), for: 'App\\Filament\\Admin\\Resources')
->discoverPages(in: app_path('Filament/Admin/Pages'), for: 'App\\Filament\\Admin\\Pages') ->discoverPages(in: app_path('Filament/Admin/Pages'), for: 'App\\Filament\\Admin\\Pages')
->databaseNotifications()
->middleware([ ->middleware([
EncryptCookies::class, EncryptCookies::class,
AddQueuedCookiesToResponse::class, AddQueuedCookiesToResponse::class,

View File

@ -46,6 +46,7 @@ class AppPanelProvider extends PanelProvider
->visible(fn (): bool => auth()->user()->canAccessPanel(Filament::getPanel('admin'))), ->visible(fn (): bool => auth()->user()->canAccessPanel(Filament::getPanel('admin'))),
]) ])
->discoverResources(in: app_path('Filament/App/Resources'), for: 'App\\Filament\\App\\Resources') ->discoverResources(in: app_path('Filament/App/Resources'), for: 'App\\Filament\\App\\Resources')
->databaseNotifications()
->middleware([ ->middleware([
EncryptCookies::class, EncryptCookies::class,
AddQueuedCookiesToResponse::class, AddQueuedCookiesToResponse::class,

View File

@ -65,6 +65,7 @@ class ServerPanelProvider extends PanelProvider
->discoverResources(in: app_path('Filament/Server/Resources'), for: 'App\\Filament\\Server\\Resources') ->discoverResources(in: app_path('Filament/Server/Resources'), for: 'App\\Filament\\Server\\Resources')
->discoverPages(in: app_path('Filament/Server/Pages'), for: 'App\\Filament\\Server\\Pages') ->discoverPages(in: app_path('Filament/Server/Pages'), for: 'App\\Filament\\Server\\Pages')
->discoverWidgets(in: app_path('Filament/Server/Widgets'), for: 'App\\Filament\\Server\\Widgets') ->discoverWidgets(in: app_path('Filament/Server/Widgets'), for: 'App\\Filament\\Server\\Widgets')
->databaseNotifications()
->middleware([ ->middleware([
EncryptCookies::class, EncryptCookies::class,
AddQueuedCookiesToResponse::class, AddQueuedCookiesToResponse::class,

View File

@ -2,8 +2,8 @@
namespace App\Services\Subusers; namespace App\Services\Subusers;
use App\Events\Server\SubUserAdded;
use App\Models\User; use App\Models\User;
use App\Notifications\AddedToServer;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use App\Models\Server; use App\Models\Server;
use App\Models\Subuser; use App\Models\Subuser;
@ -63,11 +63,7 @@ class SubuserCreationService
'permissions' => array_unique($permissions), 'permissions' => array_unique($permissions),
]); ]);
$subuser->user->notify(new AddedToServer([ event(new SubUserAdded($subuser));
'user' => $subuser->user->name_first,
'name' => $subuser->server->name,
'uuid_short' => $subuser->server->uuid_short,
]));
return $subuser; return $subuser;
}); });

View File

@ -2,11 +2,11 @@
namespace App\Services\Subusers; namespace App\Services\Subusers;
use App\Events\Server\SubUserRemoved;
use App\Exceptions\Http\Connection\DaemonConnectionException; use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Facades\Activity; use App\Facades\Activity;
use App\Models\Server; use App\Models\Server;
use App\Models\Subuser; use App\Models\Subuser;
use App\Notifications\RemovedFromServer;
use App\Repositories\Daemon\DaemonServerRepository; use App\Repositories\Daemon\DaemonServerRepository;
class SubuserDeletionService class SubuserDeletionService
@ -25,10 +25,7 @@ class SubuserDeletionService
$log->transaction(function ($instance) use ($server, $subuser) { $log->transaction(function ($instance) use ($server, $subuser) {
$subuser->delete(); $subuser->delete();
$subuser->user->notify(new RemovedFromServer([ event(new SubUserRemoved($subuser->server, $subuser->user));
'user' => $subuser->user->name_first,
'name' => $subuser->server->name,
]));
try { try {
$this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id);

View File

@ -185,7 +185,7 @@ class ProcessWebhooksTest extends TestCase
$server = $this->createServer(); $server = $this->createServer();
event(new Installed($server)); event(new Installed($server, true, true));
$this->assertDatabaseCount(Webhook::class, 1); $this->assertDatabaseCount(Webhook::class, 1);
$this->assertDatabaseHas(Webhook::class, [ $this->assertDatabaseHas(Webhook::class, [