Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions discord-webhooks/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
47 changes: 47 additions & 0 deletions discord-webhooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Discord Webhooks (by notjami)

Send Discord webhook notifications for various server events in Pelican Panel.

## Features

- Discord webhook integration
- Server status notifications (online/offline)
- Multiple webhooks support
- Event-based triggers
- Automatic status checking via scheduled task

## Supported Events

- Server Started (online)
- Server Stopped (offline)
- Server Installing
- Server Installed

## Installation

1. Copy the `discord-webhooks` folder to your Pelican Panel plugins directory
2. Install the plugin

## Server Status Detection

Server start/stop detection works via a scheduled command that checks server status every minute.

**Manual check:**
```bash
php artisan discord-webhooks:check-status
```

## Usage

1. Navigate to Admin Panel → Webhooks
2. Create a new webhook with your Discord webhook URL
3. Select which events should trigger the webhook
4. Save and test your webhook

## Configuration

Each webhook can be configured with:
- **Name**: A friendly name for the webhook
- **Webhook URL**: Your Discord webhook URL
- **Events**: Which events trigger this webhook
- **Enabled**: Toggle the webhook on/off
29 changes: 29 additions & 0 deletions discord-webhooks/database/migrations/001_create_webhooks_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?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::create('webhooks', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('webhook_url');
$table->foreignId('server_id')->nullable()->constrained('servers')->nullOnDelete();
$table->json('events')->default('[]');
$table->boolean('enabled')->default(true);
$table->timestamp('last_triggered_at')->nullable();
$table->timestamps();

// foreignId above handles FK constraint and nullOnDelete
});
}

public function down(): void
{
Schema::dropIfExists('webhooks');
}
};
17 changes: 17 additions & 0 deletions discord-webhooks/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "discord-webhooks",
"name": "Discord Webhooks",
"author": "notjami",
"version": "1.1.1",
"description": "Send Discord webhook notifications for server events",
"category": "plugin",
"url": "https://github.com/jami100YT/pelican-plugins/tree/discord-webhooks/discord-webhooks",
"update_url": null,
"namespace": "Notjami\\Webhooks",
"class": "WebhooksPlugin",
"panels": [
"server"
],
"panel_version": null,
"composer_packages": null
}
66 changes: 66 additions & 0 deletions discord-webhooks/src/Console/Commands/CheckServerStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Notjami\Webhooks\Console\Commands;

use App\Models\Server;
use App\Repositories\Daemon\DaemonServerRepository;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Notjami\Webhooks\Enums\WebhookEvent;
use Notjami\Webhooks\Models\Webhook;
use Notjami\Webhooks\Services\DiscordWebhookService;

class CheckServerStatus extends Command
{
protected $signature = 'discord-webhooks:check-status';

protected $description = 'Check server status and trigger webhooks for status changes';

public function handle(DaemonServerRepository $repository, DiscordWebhookService $webhookService): int
{
// Get all servers that have webhooks configured
$serverIds = Webhook::enabled()
->whereNotNull('server_id')
->pluck('server_id')
->unique();

// Also check servers if there are global webhooks
$hasGlobalWebhooks = Webhook::enabled()
->whereNull('server_id')
->exists();

if ($hasGlobalWebhooks) {
$servers = Server::whereNull('status')->get();
} else {
$servers = Server::whereIn('id', $serverIds)->whereNull('status')->get();
}

foreach ($servers as $server) {
try {
$details = $repository->setServer($server)->getDetails();
$currentState = $details['state'] ?? 'offline';

$cacheKey = "webhook_server_status_{$server->id}";
$previousState = Cache::get($cacheKey, 'unknown');
// Always refresh the cache TTL, even if state hasn't changed
Cache::put($cacheKey, $currentState, now()->addHours(24));

if ($previousState !== $currentState) {
if ($previousState !== 'unknown') {
if ($currentState === 'running') {
$webhookService->triggerEvent(WebhookEvent::ServerStarted, $server);
$this->info("Server {$server->name} started - webhook triggered");
} elseif (in_array($currentState, ['offline', 'stopped'])) {
$webhookService->triggerEvent(WebhookEvent::ServerStopped, $server);
$this->info("Server {$server->name} stopped - webhook triggered");
}
}
}
} catch (\Exception $e) {
$this->error("Failed to check server {$server->name}: {$e->getMessage()}");
}
}

return self::SUCCESS;
}
}
51 changes: 51 additions & 0 deletions discord-webhooks/src/Enums/WebhookEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Notjami\Webhooks\Enums;

enum WebhookEvent: string
{
case ServerStarted = 'server_started';
case ServerStopped = 'server_stopped';
case ServerInstalling = 'server_installing';
case ServerInstalled = 'server_installed';

public function getLabel(): string
{
return match ($this) {
self::ServerStarted => 'Server Started',
self::ServerStopped => 'Server Stopped',
self::ServerInstalling => 'Server Installing',
self::ServerInstalled => 'Server Installed',
};
}

public function getDescription(): string
{
return match ($this) {
self::ServerStarted => 'Triggered when a server comes online',
self::ServerStopped => 'Triggered when a server goes offline',
self::ServerInstalling => 'Triggered when a server starts installing',
self::ServerInstalled => 'Triggered when a server finishes installation',
};
}

public function getColor(): string
{
return match ($this) {
self::ServerStarted => '3066993', // Green
self::ServerStopped => '15158332', // Red
self::ServerInstalling => '15105570', // Orange
self::ServerInstalled => '3447003', // Blue
};
}

public function getEmoji(): string
{
return match ($this) {
self::ServerStarted => '🟢',
self::ServerStopped => '🔴',
self::ServerInstalling => '🔧',
self::ServerInstalled => '✅',
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Notjami\Webhooks\Filament\Server\Resources\Webhooks\Pages;

use Filament\Resources\Pages\ManageRecords;
use Notjami\Webhooks\Filament\Server\Resources\Webhooks\WebhookResource;

class ManageWebhooks extends ManageRecords
{
protected static string $resource = WebhookResource::class;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php

namespace Notjami\Webhooks\Filament\Server\Resources\Webhooks;

use Filament\Actions\Action;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Notjami\Webhooks\Enums\WebhookEvent;
use Notjami\Webhooks\Filament\Server\Resources\Webhooks\Pages\ManageWebhooks;
use Notjami\Webhooks\Models\Webhook;
use Notjami\Webhooks\Services\DiscordWebhookService;
class WebhookResource extends Resource
{
protected static ?string $model = Webhook::class;

protected static string|\BackedEnum|null $navigationIcon = 'tabler-webhook';

public static function getNavigationLabel(): string
{
return 'Discord Webhooks';
}

public static function getModelLabel(): string
{
return 'Discord Webhook';
}

public static function getPluralModelLabel(): string
{
return 'Discord Webhooks';
}

public static function getNavigationBadge(): ?string
{
return (string) static::getEloquentQuery()->count() ?: null;
}

public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('name')
->label('Name')
->searchable()
->sortable(),
TextColumn::make('webhook_url')
->label('Webhook URL')
->limit(30)
->formatStateUsing(function ($state) {
// Mask the URL: show scheme://host/...****
if (empty($state)) return '';
$parsed = parse_url($state);
if (!$parsed || !isset($parsed['scheme'], $parsed['host'])) return '••••••';
$last4 = substr($parsed['path'] ?? '', -4);
$masked = $parsed['scheme'] . '://' . $parsed['host'] . '/...';
if ($last4) {
$masked .= $last4;
}
return $masked;
})
->tooltip('••••••'),
TextColumn::make('events')
->label('Events')
->badge()
->formatStateUsing(function ($state) {
$events = is_array($state) ? $state : (array) $state;
return collect($events)
->map(fn ($event) => WebhookEvent::tryFrom($event))
->filter()
->map(fn ($event) => $event->getLabel())
->join(', ');
}),
IconColumn::make('enabled')
->label('Enabled')
->boolean(),
TextColumn::make('last_triggered_at')
->label('Last Triggered')
->dateTime()
->placeholder('Never'),
])
->recordActions([
Action::make('test')
->label('Test')
->icon('tabler-send')
->color('info')
->requiresConfirmation()
->modalHeading('Test Webhook')
->modalDescription('This will send a test message to the webhook URL.')
->action(function (Webhook $record, DiscordWebhookService $service) {
$success = $service->sendTestMessage($record);

if ($success) {
Notification::make()
->title('Test message sent!')
->success()
->send();
} else {
Notification::make()
->title('Failed to send test message')
->danger()
->send();
}
}),
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
CreateAction::make()
->createAnother(false),
])
->emptyStateIcon('tabler-webhook')
->emptyStateDescription('')
->emptyStateHeading('No webhooks configured');
}

public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('name')
->label('Name')
->placeholder('My Discord Webhook')
->required()
->maxLength(255)
->columnSpanFull(),
TextInput::make('webhook_url')
->label('Discord Webhook URL')
->placeholder('https://discord.com/api/webhooks/...')
->required()
->url()
->regex('/^https:\/\/discord\\.com\/api\/webhooks\/.+/')
->maxLength(500)
->columnSpanFull(),
Select::make('events')
->label('Events')
->multiple()
->options(collect(WebhookEvent::cases())->mapWithKeys(fn ($event) => [
$event->value => $event->getLabel(),
]))
->required()
->columnSpanFull(),
Toggle::make('enabled')
->label('Enabled')
->default(true)
->inline(false),
]);
}

public static function getPages(): array
{
return [
'index' => ManageWebhooks::route('/'),
];
}
}
Loading
Loading