mirror of
https://github.com/pelican-dev/panel.git
synced 2026-02-08 11:19:53 +03:00
Compare commits
7 Commits
main
...
vehikl/mak
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2da5666ce9 | ||
|
|
e4221bc606 | ||
|
|
00889a8004 | ||
|
|
c948314e21 | ||
|
|
dd2fdd15a1 | ||
|
|
59f4679fd1 | ||
|
|
b838c87af7 |
@@ -4,6 +4,7 @@ namespace App\Filament\Admin\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
use App\Filament\Admin\Resources\WebhookResource\Pages\EditWebhookConfiguration;
|
||||
use App\Filament\Admin\Resources\WebhookResource\RelationManagers\EventsRelationManager;
|
||||
use App\Livewire\AlertBanner;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
@@ -23,6 +24,7 @@ use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
@@ -161,17 +163,21 @@ class WebhookResource extends Resource
|
||||
->view('filament.components.webhooksection')
|
||||
->aside()
|
||||
->formBefore(),
|
||||
Section::make(trans('admin/webhook.events'))
|
||||
/*Section::make(trans('admin/webhook.events'))
|
||||
->schema([
|
||||
CheckboxList::make('events')
|
||||
->live()
|
||||
->options(fn () => WebhookConfiguration::filamentCheckboxList())
|
||||
->searchable()
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->required(),
|
||||
]),
|
||||
CheckboxList::make('webhookEvents')
|
||||
->relationship('webhookEvents')
|
||||
->live()
|
||||
->options(fn () => WebhookConfiguration::filamentCheckboxList())
|
||||
->searchable()
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->required()
|
||||
->before(function (array $state) {
|
||||
dd($state);
|
||||
}),
|
||||
]),*/
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -343,4 +349,12 @@ class WebhookResource extends Resource
|
||||
'edit' => Pages\EditWebhookConfiguration::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
/** @return class-string<RelationManager>[] */
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
EventsRelationManager::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\WebhookResource\RelationManagers;
|
||||
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Models\WebhookEvent;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
/**
|
||||
* @method WebhookConfiguration getOwnerRecord()
|
||||
*/
|
||||
class EventsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'webhookEvents';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->heading('')
|
||||
->columns([
|
||||
TextColumn::make('id'),
|
||||
TextColumn::make('name'),
|
||||
])
|
||||
->headerActions([
|
||||
Action::make('create')
|
||||
->form(fn () => [
|
||||
TextInput::make('name')
|
||||
->inlineLabel()
|
||||
->live()
|
||||
->required(),
|
||||
])
|
||||
->action(function (array $data) {
|
||||
$id = WebhookEvent::firstOrCreate([
|
||||
'name' => $data['name'],
|
||||
]);
|
||||
$this->getOwnerRecord()->webhookEvents()->sync([$id]);
|
||||
}),
|
||||
])
|
||||
->actions([
|
||||
DeleteAction::make()
|
||||
->authorize(fn (WebhookConfiguration $config) => auth()->user()->can('delete', $config)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -18,59 +18,71 @@ class ProcessWebhook implements ShouldQueue
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
* @param array<mixed>|string|null $data
|
||||
*/
|
||||
public function __construct(
|
||||
private WebhookConfiguration $webhookConfiguration,
|
||||
private string $eventName,
|
||||
private array $data
|
||||
private array|string|null $data = null
|
||||
) {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$data = $this->data[0] ?? [];
|
||||
if (count($data) === 1) {
|
||||
$data = reset($data);
|
||||
}
|
||||
$data = is_array($data) ? $data : (json_decode($data, true) ?? []);
|
||||
$data['event'] = $this->webhookConfiguration->transformClassName($this->eventName);
|
||||
$payload = is_array($this->data) ? $this->data : (json_decode($this->data, true) ?? []);
|
||||
$payload['event'] = $this->webhookConfiguration->transformClassName($this->eventName);
|
||||
|
||||
if ($this->webhookConfiguration->type === WebhookType::Discord) {
|
||||
$payload = json_encode($this->webhookConfiguration->payload);
|
||||
$tmp = $this->webhookConfiguration->replaceVars($data, $payload);
|
||||
$data = json_decode($tmp, true);
|
||||
|
||||
$embeds = data_get($data, 'embeds');
|
||||
if ($embeds) {
|
||||
foreach ($embeds as &$embed) {
|
||||
if (data_get($embed, 'has_timestamp')) {
|
||||
$embed['timestamp'] = Carbon::now();
|
||||
unset($embed['has_timestamp']);
|
||||
}
|
||||
}
|
||||
$data['embeds'] = $embeds;
|
||||
}
|
||||
$payload = $this->convertToDiscord($payload);
|
||||
}
|
||||
|
||||
try {
|
||||
$customHeaders = $this->webhookConfiguration->headers;
|
||||
$customHeaders = $this->webhookConfiguration->headers ?: [];
|
||||
$headers = [];
|
||||
foreach ($customHeaders as $key => $value) {
|
||||
$headers[$key] = $this->webhookConfiguration->replaceVars($data, $value);
|
||||
$headers[$key] = $this->webhookConfiguration->replaceVars($payload, $value);
|
||||
}
|
||||
|
||||
Http::withHeaders($headers)->post($this->webhookConfiguration->endpoint, $data)->throw();
|
||||
Http::withHeaders($headers)->post($this->webhookConfiguration->endpoint, $payload)->throw();
|
||||
$successful = now();
|
||||
} catch (Exception $exception) {
|
||||
report($exception->getMessage());
|
||||
$successful = null;
|
||||
}
|
||||
|
||||
$this->logWebhookCall($payload, $successful);
|
||||
}
|
||||
|
||||
private function logWebhookCall(array $payload, Carbon $success): void
|
||||
{
|
||||
$this->webhookConfiguration->webhooks()->create([
|
||||
'payload' => $data,
|
||||
'successful_at' => $successful,
|
||||
'payload' => $payload,
|
||||
'successful_at' => $success,
|
||||
'event' => $this->eventName,
|
||||
'endpoint' => $this->webhookConfiguration->endpoint,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @return array
|
||||
*/
|
||||
public function convertToDiscord(mixed $data): array
|
||||
{
|
||||
$payload = json_encode($this->webhookConfiguration->payload);
|
||||
$tmp = $this->webhookConfiguration->replaceVars($data, $payload);
|
||||
$data = json_decode($tmp, true);
|
||||
|
||||
$embeds = data_get($data, 'embeds');
|
||||
if ($embeds) {
|
||||
// copied from previous, is the & needed?
|
||||
foreach ($embeds as &$embed) {
|
||||
if (data_get($embed, 'has_timestamp')) {
|
||||
$embed['timestamp'] = Carbon::now();
|
||||
unset($embed['has_timestamp']);
|
||||
}
|
||||
}
|
||||
$data['embeds'] = $embeds;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ use Illuminate\Database\Eloquent\Model;
|
||||
*/
|
||||
class Webhook extends Model
|
||||
{
|
||||
use HasFactory, MassPrunable;
|
||||
use MassPrunable;
|
||||
|
||||
protected $fillable = ['payload', 'successful_at', 'event', 'endpoint'];
|
||||
|
||||
public function casts()
|
||||
public function casts(): array
|
||||
{
|
||||
return [
|
||||
'payload' => 'array',
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Jobs\ProcessWebhook;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -192,7 +193,7 @@ class WebhookConfiguration extends Model
|
||||
$eventName ??= 'eloquent.created: '.Server::class;
|
||||
$eventData ??= $this->getWebhookSampleData();
|
||||
|
||||
ProcessWebhook::dispatch($this, $eventName, [$eventData]);
|
||||
ProcessWebhook::dispatch($this, $eventName, $eventData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,4 +232,9 @@ class WebhookConfiguration extends Model
|
||||
'id' => 2,
|
||||
];
|
||||
}
|
||||
|
||||
public function webhookEvents(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(WebhookEvent::class, 'webhook_configurations_events', 'configuration_id', 'event_id');
|
||||
}
|
||||
}
|
||||
|
||||
26
app/Models/WebhookEvent.php
Normal file
26
app/Models/WebhookEvent.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
/**
|
||||
* @property string $name
|
||||
*/
|
||||
class WebhookEvent extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
];
|
||||
|
||||
public function configurations(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(WebhookConfiguration::class, 'webhook_configurations_events', 'event_id', 'configuration_id');
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ class WebhookConfigurationFactory extends Factory
|
||||
return [
|
||||
'endpoint' => fake()->url(),
|
||||
'description' => fake()->sentence(),
|
||||
'events' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
18
database/Factories/WebhookEventFactory.php
Normal file
18
database/Factories/WebhookEventFactory.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\WebhookEvent;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class WebhookEventFactory extends Factory
|
||||
{
|
||||
protected $model = WebhookEvent::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->word,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
|
||||
/*Schema::table('webhook_configurations', function (Blueprint $table) {
|
||||
$table->dropColumn('events');
|
||||
});*/ // TODO: convert old format
|
||||
|
||||
Schema::create('webhook_events', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
});
|
||||
Schema::create('webhook_configurations_events', function (Blueprint $table) {
|
||||
$table->foreignId('event_id')->references('id')->on('webhook_events')->onDelete('cascade');
|
||||
$table->foreignId('configuration_id')->references('id')->on('webhook_configurations')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('webhook_events');
|
||||
Schema::dropIfExists('webhook_configurations_events');
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Feature\Webhooks;
|
||||
namespace App\Tests\Unit\Webhooks;
|
||||
|
||||
use App\Jobs\ProcessWebhook;
|
||||
use App\Models\Server;
|
||||
@@ -68,7 +68,7 @@ class DispatchWebhooksTest extends TestCase
|
||||
'events' => ['eloquent.created: '.Server::class],
|
||||
]);
|
||||
|
||||
$webhookConfig->update(['events' => 'eloquent.deleted: '.Server::class]);
|
||||
$webhookConfig->update(['events' => ['eloquent.deleted: '.Server::class]]);
|
||||
|
||||
$this->createServer();
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Feature\Webhooks;
|
||||
namespace App\Tests\Unit\Webhooks;
|
||||
|
||||
use App\Events\Server\Installed;
|
||||
use App\Jobs\ProcessWebhook;
|
||||
use App\Models\Server;
|
||||
use App\Models\Webhook;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Models\WebhookEvent;
|
||||
use App\Tests\TestCase;
|
||||
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
||||
use Illuminate\Http\Client\Request;
|
||||
@@ -26,9 +27,10 @@ class ProcessWebhooksTest extends TestCase
|
||||
|
||||
public function test_it_sends_a_single_webhook(): void
|
||||
{
|
||||
$webhook = WebhookConfiguration::factory()->create([
|
||||
'events' => [$eventName = 'eloquent.created: '.Server::class],
|
||||
]);
|
||||
$eventName = 'eloquent.created: '.Server::class;
|
||||
$webhook = WebhookConfiguration::factory()
|
||||
->has(WebhookEvent::factory()->state(['name' => $eventName]), 'webhookEvents')
|
||||
->create(['events' => []]);
|
||||
|
||||
Http::fake([$webhook->endpoint => Http::response()]);
|
||||
|
||||
@@ -64,10 +66,10 @@ class ProcessWebhooksTest extends TestCase
|
||||
ProcessWebhook::dispatchSync(
|
||||
$webhook,
|
||||
'eloquent.created: '.Server::class,
|
||||
$data,
|
||||
[$data],
|
||||
);
|
||||
|
||||
$this->assertCount(1, cache()->get("webhooks.$eventName"));
|
||||
$this->assertCount(1, cache()->get("webhooks.$eventName", []));
|
||||
$this->assertEquals($webhook->id, cache()->get("webhooks.$eventName")->first()->id);
|
||||
|
||||
Http::assertSentCount(1);
|
||||
Reference in New Issue
Block a user