diff --git a/composer.json b/composer.json index 93c5df9..983a9dd 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "dashboard/unifi", "description": "UniFi network management, WiFi stats, and captive portal authentication for the Dashboard platform", - "version": "1.5.3", + "version": "1.5.4", "type": "library", "license": "MIT", "autoload": { diff --git a/database/migrations/2026_05_24_000002_add_templates_and_tracked_clients_to_unifi_webhook_configs.php b/database/migrations/2026_05_24_000002_add_templates_and_tracked_clients_to_unifi_webhook_configs.php new file mode 100644 index 0000000..77bb7ef --- /dev/null +++ b/database/migrations/2026_05_24_000002_add_templates_and_tracked_clients_to_unifi_webhook_configs.php @@ -0,0 +1,32 @@ +json('tracked_clients')->nullable()->after('device_filter'); + } + if (! Schema::hasColumn('unifi_webhook_configs', 'templates')) { + $table->json('templates')->nullable()->after('tracked_clients'); + } + }); + } + + public function down(): void + { + Schema::table('unifi_webhook_configs', function (Blueprint $table) { + if (Schema::hasColumn('unifi_webhook_configs', 'templates')) { + $table->dropColumn('templates'); + } + if (Schema::hasColumn('unifi_webhook_configs', 'tracked_clients')) { + $table->dropColumn('tracked_clients'); + } + }); + } +}; diff --git a/src/Http/Controllers/WebhookController.php b/src/Http/Controllers/WebhookController.php index 22dba74..92847e7 100644 --- a/src/Http/Controllers/WebhookController.php +++ b/src/Http/Controllers/WebhookController.php @@ -67,6 +67,25 @@ class WebhookController extends Controller } public function test(WebhookConfig $webhook) + { + return $this->fireTest($webhook->url, $webhook->secret); + } + + /** + * Test an arbitrary URL+secret before the webhook is saved. Lets the + * operator validate their endpoint from the form without first + * committing a row. + */ + public function testUrl(Request $request) + { + $data = $request->validate([ + 'url' => 'required|url|max:500', + 'secret' => 'nullable|string|max:255', + ]); + return $this->fireTest($data['url'], $data['secret'] ?? null); + } + + private function fireTest(string $url, ?string $secret) { $payload = [ 'event' => 'test', @@ -75,13 +94,17 @@ class WebhookController extends Controller ]; $headers = ['Content-Type' => 'application/json']; - if ($webhook->secret) { - $headers['X-Webhook-Signature'] = hash_hmac('sha256', json_encode($payload), $webhook->secret); + if ($secret) { + $headers['X-Webhook-Signature'] = hash_hmac('sha256', json_encode($payload), $secret); } try { - $response = \Illuminate\Support\Facades\Http::withHeaders($headers)->timeout(10)->post($webhook->url, $payload); - return response()->json(['ok' => true, 'status' => $response->status()]); + $response = \Illuminate\Support\Facades\Http::withHeaders($headers)->timeout(10)->post($url, $payload); + return response()->json([ + 'ok' => $response->successful(), + 'status' => $response->status(), + 'body' => mb_substr((string) $response->body(), 0, 500), + ]); } catch (\Throwable $e) { return response()->json(['ok' => false, 'error' => $e->getMessage()], 422); } diff --git a/src/routes/unifi.php b/src/routes/unifi.php index 8361fe2..5234127 100644 --- a/src/routes/unifi.php +++ b/src/routes/unifi.php @@ -89,6 +89,7 @@ Route::middleware(['web', 'auth', 'app.access:unifi']) Route::put('/settings/webhooks/{webhook}', [WebhookController::class, 'update']) ->name('webhooks.update'); Route::delete('/settings/webhooks/{webhook}', [WebhookController::class, 'destroy'])->name('webhooks.destroy'); Route::post('/settings/webhooks/{webhook}/test', [WebhookController::class, 'test']) ->name('webhooks.test'); + Route::post('/settings/webhooks/test-url', [WebhookController::class, 'testUrl'])->name('webhooks.test-url'); }); });