Compare commits

..

2 Commits

Author SHA1 Message Date
Boy132
2ec9a008e7 fix namespace 2026-05-04 10:42:57 +02:00
Boy132
fcd5d005ed add backup completed notifications 2026-05-04 10:28:16 +02:00
14 changed files with 143 additions and 6 deletions

View File

@@ -41,8 +41,17 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
};
}
public function getColor(): string
public function getColor(bool $hex = false): string
{
if ($hex) {
return match ($this) {
self::Created, self::Restarting => '#2563EB',
self::Starting, self::Paused, self::Removing, self::Stopping => '#D97706',
self::Running => '#22C55E',
self::Exited, self::Missing, self::Dead, self::Offline => '#EF4444',
};
}
return match ($this) {
self::Created => 'primary',
self::Starting => 'warning',

View File

@@ -25,8 +25,16 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
};
}
public function getColor(): string
public function getColor(bool $hex = false): string
{
if ($hex) {
return match ($this) {
self::Installing, self::RestoringBackup => '#2563EB',
self::Suspended => '#D97706',
self::InstallFailed, self::ReinstallFailed => '#EF4444',
};
}
return match ($this) {
self::Installing => 'primary',
self::InstallFailed => 'danger',

View File

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

View File

@@ -703,6 +703,16 @@ class Settings extends Page implements HasSchemas
->formatStateUsing(fn ($state): bool => (bool) $state)
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_REINSTALL_NOTIFICATION', (bool) $state))
->default(env('PANEL_SEND_REINSTALL_NOTIFICATION', config('panel.email.send_reinstall_notification'))),
Toggle::make('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION')
->label(trans('admin/setting.misc.mail_notifications.backup_completed'))
->onIcon(TablerIcon::Check)
->offIcon(TablerIcon::X)
->onColor('success')
->offColor('danger')
->live()
->formatStateUsing(fn ($state): bool => (bool) $state)
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION', (bool) $state))
->default(env('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION', config('panel.email.send_backup_completed_notification'))),
]),
Section::make(trans('admin/setting.misc.connections.title'))
->description(trans('admin/setting.misc.connections.helper'))

View File

@@ -21,9 +21,9 @@ class TagsFilter extends BaseFilter
{
parent::setUp();
$this->query(fn (Builder $query, array $data) => $query->when($data['tag'] ?? null, fn (Builder $query, $tag) => $query->whereJsonContains('tags', $tag)));
$this->query(fn (Builder $query, array $data) => $query->when($data['tag'], fn (Builder $query, $tag) => $query->whereJsonContains('tags', $tag)));
$this->indicateUsing(fn (array $data) => ($data['tag'] ?? null) ? 'Tag: ' . $data['tag'] : null);
$this->indicateUsing(fn (array $data) => $data['tag'] ? 'Tag: ' . $data['tag'] : null);
$this->resetState(['tag' => null]);

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api\Remote\Backups;
use App\Events\Server\BackupCompleted;
use App\Exceptions\DisplayException;
use App\Exceptions\Http\HttpForbiddenException;
use App\Extensions\Backups\BackupManager;
@@ -79,6 +80,8 @@ class BackupStatusController extends Controller
}
});
event(new BackupCompleted($model));
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Listeners\Server;
use App\Events\Server\BackupCompleted;
use App\Filament\Server\Resources\Backups\Pages\ListBackups;
use App\Notifications\BackupCompleted as BackupCompletedNotification;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
class BackupCompletedListener
{
public function handle(BackupCompleted $event): void
{
$event->backup->loadMissing('server');
$event->backup->server->loadMissing('user');
$locale = $event->backup->server->user->language ?? 'en';
Notification::make()
->status($event->backup->is_successful ? 'success' : 'danger')
->title(trans('notifications.backup_' . ($event->backup->is_successful ? 'completed' : 'failed'), locale: $locale))
->body(trans('notifications.backup_body', ['name' => $event->backup->name, 'server' => $event->backup->server->name], $locale))
->actions([
Action::make('exclude_view')
->button()
->label(trans('notifications.view_backups', locale: $locale))
->markAsRead()
->url(fn () => ListBackups::getUrl(panel: 'server', tenant: $event->backup->server)),
])
->sendToDatabase($event->backup->server->user);
if (config()->get('panel.email.send_backup_completed_notification', true)) {
$event->backup->server->user->notify(new BackupCompletedNotification($event->backup));
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Notifications;
use App\Filament\Server\Resources\Backups\Pages\ListBackups;
use App\Models\Backup;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class BackupCompleted extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(public Backup $backup) {}
/** @return string[] */
public function via(): array
{
return ['mail'];
}
public function toMail(User $notifiable): MailMessage
{
$locale = $notifiable->language ?? 'en';
return (new MailMessage())
->greeting(trans('mail.greeting', ['name' => $notifiable->username], $locale))
->line(trans('mail.backup_completed.body_' . ($this->backup->is_successful ? 'success' : 'failed'), locale: $locale))
->line(trans('mail.backup_completed.backup_name', ['name' => $this->backup->name], $locale))
->line(trans('mail.backup_completed.server_name', ['name' => $this->backup->server->name], $locale))
->action(trans('mail.backup_completed.action', locale: $locale), ListBackups::getUrl(panel: 'server', tenant: $this->backup->server));
}
}

View File

@@ -49,6 +49,8 @@ return [
'send_install_notification' => env('PANEL_SEND_INSTALL_NOTIFICATION', true),
// Should an email be sent to a server owner whenever their server is reinstalled?
'send_reinstall_notification' => env('PANEL_SEND_REINSTALL_NOTIFICATION', true),
// Should an email be sent to a server owner whenever a backup is completed?
'send_backup_completed_notification' => env('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION', true),
],
'filament' => [

View File

@@ -118,6 +118,7 @@ return [
'helper' => 'Toggle which mail notifications should be sent to Users.',
'server_installed' => 'Server Installed',
'server_reinstalled' => 'Server Reinstalled',
'backup_completed' => 'Backup Completed',
],
'connections' => [
'title' => 'Connections',

View File

@@ -28,6 +28,14 @@ return [
'action' => 'Login and Begin Using',
],
'backup_completed' => [
'body_success' => 'The backup was created successfully.',
'body_failed' => 'The backup creation failed.',
'backup_name' => 'Backup Name: :name',
'server_name' => 'Server Name: :name',
'action' => 'View Backups',
],
'mail_tested' => [
'subject' => 'Panel Test Message',
'body' => 'This is a test of the Panel mail system. You\'re good to go!',

View File

@@ -6,6 +6,10 @@ return [
'installation_failed' => 'Server Installation Failed',
'reinstallation_completed' => 'Server Reinstallation Completed',
'reinstallation_failed' => 'Server Reinstallation Failed',
'backup_completed' => 'Backup Completed',
'backup_failed' => 'Backup Failed',
'backup_body' => 'Backup ":name" for server ":server"',
'view_backups' => 'View Backups',
'failed' => 'Failed',
'user_added' => [
'title' => 'Added to Server',

View File

@@ -6,7 +6,7 @@
<div class="relative cursor-pointer"
x-on:click="{{ $component->redirectUrl() }}"
x-on:auxclick.prevent="if ($event.button === 1) {{ $component->redirectUrl(true) }}">
<div class="absolute left-0 top-1 bottom-0 w-1 rounded-lg fi-color fi-color-warning fi-bg-color-600" style="background-color: var(--bg);"></div>
<div class="absolute left-0 top-1 bottom-0 w-1 rounded-lg" style="background-color: #D97706;"></div>
<div class="flex-1 dark:bg-gray-800 dark:text-white rounded-lg overflow-hidden p-3">
@if($backgroundImage)

View File

@@ -14,7 +14,9 @@
x-on:click="{{ $component->redirectUrl() }}"
x-on:auxclick.prevent="if ($event.button === 1) {{ $component->redirectUrl(true) }}">
<div class="absolute left-0 top-1 bottom-0 w-1 rounded-lg fi-color fi-color-{{ $server->condition->getColor() }} fi-bg-color-600" style="background-color: var(--bg);"> </div>
<div class="absolute left-0 top-1 bottom-0 w-1 rounded-lg"
style="background-color: {{ $server->condition->getColor(true) }};">
</div>
<div class="flex-1 dark:bg-gray-800 dark:text-white rounded-lg overflow-hidden p-3">
@if($backgroundImage)