1 Commits

Author SHA1 Message Date
e8796d443e fix(webhooks): test endpoint formats payload per platform; add missing column
* The Test URL button was POSTing a generic {event, timestamp, data}
  envelope to every endpoint. Google Chat / Slack / Discord / Teams
  reject anything that isn't their specific shape — so a successful
  Laravel request still got a 400 back from the platform, making the
  test look broken. The real webhook events already handle this via
  WebhookCheckService::formatPayloadForPlatform; that helper is now
  exposed as a public static (buildPlatformPayload) and the test
  endpoint uses the same code path, so the test exercises the same
  format real events will.
* unifi_device_states was missing a consecutive_count column the
  WebhookCheckService inserts on every snapshot capture. The scheduler
  was throwing "Unknown column 'consecutive_count'" once a minute.
  Added an idempotent migration.

v1.6.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:34:42 -04:00
4 changed files with 48 additions and 7 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "dashboard/unifi",
"description": "UniFi network management, WiFi stats, and captive portal authentication for the Dashboard platform",
"version": "1.6.0",
"version": "1.6.1",
"type": "library",
"license": "MIT",
"autoload": {

View File

@@ -0,0 +1,26 @@
<?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::table('unifi_device_states', function (Blueprint $table) {
if (! Schema::hasColumn('unifi_device_states', 'consecutive_count')) {
$table->unsignedSmallInteger('consecutive_count')->default(0)->after('in_alert');
}
});
}
public function down(): void
{
Schema::table('unifi_device_states', function (Blueprint $table) {
if (Schema::hasColumn('unifi_device_states', 'consecutive_count')) {
$table->dropColumn('consecutive_count');
}
});
}
};

View File

@@ -87,11 +87,17 @@ class WebhookController extends Controller
private function fireTest(string $url, ?string $secret)
{
$payload = [
$message = '✅ Test webhook from ' . config('app.name') . ' — endpoint is reachable.';
$genericPayload = [
'event' => 'test',
'timestamp' => now()->toIso8601String(),
'data' => ['message' => 'This is a test webhook from ' . config('app.name')],
'message' => $message,
'data' => ['message' => $message],
];
// Shape the payload to match the target platform (Google Chat,
// Slack, Discord, Teams) so the test exercises the same code
// path real events use.
$payload = \Dashboard\Unifi\Services\WebhookCheckService::buildPlatformPayload($url, $message, $genericPayload);
$headers = ['Content-Type' => 'application/json'];
if ($secret) {

View File

@@ -581,10 +581,19 @@ class WebhookCheckService
private function formatPayloadForPlatform(string $url, string $message, array $fullPayload): array
{
if (str_contains($url, 'chat.googleapis.com')) return ['text' => $message];
if (str_contains($url, 'hooks.slack.com')) return ['text' => $message];
if (str_contains($url, 'discord.com/api/webhooks')) return ['content' => $message];
if (str_contains($url, 'webhook.office.com') || str_contains($url, 'workflows.office.com')) return ['text' => $message];
return self::buildPlatformPayload($url, $message, $fullPayload);
}
/**
* Public/static helper so the test-webhook endpoint produces the
* same per-platform payload shape that real events do.
*/
public static function buildPlatformPayload(string $url, string $message, array $fullPayload): array
{
if (str_contains($url, 'chat.googleapis.com')) return ['text' => $message];
if (str_contains($url, 'hooks.slack.com')) return ['text' => $message];
if (str_contains($url, 'discord.com/api/webhooks')) return ['content' => $message];
if (str_contains($url, 'webhook.office.com') || str_contains($url, 'workflows.office.com')) return ['text' => $message];
return $fullPayload;
}