feat: initial commit — UniFi snap-in package
Full UniFi dashboard snap-in including: - WiFi/client/device stats with time-series snapshots - Client Dashboard with traffic, satisfaction, signal, download charts - Webhook alerting with debounced offline/online detection - AP snapshot collection, client snapshot collection - Device classification (type and OS) from OUI/hostname heuristics - Webhook cooldown, templates, and multi-platform delivery Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
<?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
|
||||
{
|
||||
// VLAN mapping rules: OU/group → VLAN + device limit + session duration
|
||||
Schema::create('unifi_vlan_mappings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('group_name', 255); // Google OU or group name
|
||||
$table->string('match_type', 20) // 'ou', 'group', 'email_domain', 'default'
|
||||
->default('group');
|
||||
$table->string('match_value', 255); // the value to match against
|
||||
$table->unsignedSmallInteger('vlan_id'); // VLAN to assign
|
||||
$table->unsignedTinyInteger('max_devices') // max concurrent devices
|
||||
->default(1);
|
||||
$table->unsignedInteger('session_minutes') // portal session duration
|
||||
->default(720);
|
||||
$table->unsignedSmallInteger('sort_order')
|
||||
->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// Known MAC addresses → direct VLAN assignment (no portal needed)
|
||||
Schema::create('unifi_known_macs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('mac_address', 17)->unique(); // aa:bb:cc:dd:ee:ff
|
||||
$table->string('device_name', 255)->nullable();
|
||||
$table->string('device_type', 100)->nullable(); // chromebook, printer, phone, etc.
|
||||
$table->string('owner', 255)->nullable(); // who it belongs to
|
||||
$table->unsignedSmallInteger('vlan_id');
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// Active portal sessions (tracks who's connected via captive portal)
|
||||
Schema::create('unifi_portal_sessions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('mac_address', 17);
|
||||
$table->string('device_hostname', 255)->nullable();
|
||||
$table->string('device_os', 100)->nullable(); // detected OS
|
||||
$table->string('device_type', 100)->nullable(); // detected device category
|
||||
$table->string('ssid', 100)->nullable();
|
||||
$table->unsignedSmallInteger('vlan_id')->nullable();
|
||||
$table->string('ap_mac', 17)->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamp('authorized_at');
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['user_id', 'is_active']);
|
||||
$table->index('mac_address');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('unifi_portal_sessions');
|
||||
Schema::dropIfExists('unifi_known_macs');
|
||||
Schema::dropIfExists('unifi_vlan_mappings');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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('unifi_webhook_configs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('url', 500);
|
||||
$table->string('secret', 255)->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->json('events'); // ['device_offline','wan_down',...]
|
||||
$table->json('thresholds')->nullable(); // {"client_count":50,"cu_threshold":80,...}
|
||||
$table->json('device_filter')->nullable(); // ["aa:bb:cc:dd:ee:ff",...]
|
||||
$table->unsignedInteger('cooldown_minutes')->default(15);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('unifi_webhook_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('webhook_config_id')->constrained('unifi_webhook_configs')->cascadeOnDelete();
|
||||
$table->string('event_type', 100);
|
||||
$table->json('payload');
|
||||
$table->unsignedSmallInteger('response_code')->nullable();
|
||||
$table->text('response_body')->nullable();
|
||||
$table->timestamp('fired_at');
|
||||
$table->index(['webhook_config_id', 'event_type', 'fired_at']);
|
||||
});
|
||||
|
||||
Schema::create('unifi_device_states', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('device_mac', 17)->unique();
|
||||
$table->string('device_name', 255)->nullable();
|
||||
$table->boolean('was_online')->default(true);
|
||||
$table->timestamp('last_seen_at')->nullable();
|
||||
$table->timestamp('updated_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('unifi_webhook_logs');
|
||||
Schema::dropIfExists('unifi_webhook_configs');
|
||||
Schema::dropIfExists('unifi_device_states');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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('unifi_ap_snapshots', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('ap_mac', 17)->index();
|
||||
$table->string('ap_name', 255)->nullable();
|
||||
$table->unsignedSmallInteger('num_sta')->default(0);
|
||||
$table->unsignedBigInteger('rx_bytes')->default(0);
|
||||
$table->unsignedBigInteger('tx_bytes')->default(0);
|
||||
$table->unsignedInteger('tx_retries')->default(0);
|
||||
$table->unsignedInteger('tx_dropped')->default(0);
|
||||
$table->unsignedInteger('rx_dropped')->default(0);
|
||||
$table->unsignedInteger('tx_errors')->default(0);
|
||||
$table->unsignedInteger('rx_errors')->default(0);
|
||||
$table->unsignedTinyInteger('satisfaction')->nullable();
|
||||
$table->unsignedTinyInteger('cu_2g')->nullable();
|
||||
$table->unsignedTinyInteger('cu_5g')->nullable();
|
||||
$table->unsignedTinyInteger('cu_6g')->nullable();
|
||||
$table->timestamp('captured_at')->index();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('unifi_ap_snapshots');
|
||||
}
|
||||
};
|
||||
@@ -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('unifi_client_snapshots', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('mac', 17)->index();
|
||||
$table->string('name', 255)->nullable();
|
||||
$table->string('dev_cat', 64)->nullable()->index();
|
||||
$table->string('os_name', 64)->nullable()->index();
|
||||
$table->boolean('is_wired')->default(false);
|
||||
$table->unsignedBigInteger('rx_bytes')->default(0);
|
||||
$table->unsignedBigInteger('tx_bytes')->default(0);
|
||||
$table->unsignedTinyInteger('satisfaction')->nullable();
|
||||
$table->timestamp('captured_at')->index();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('unifi_client_snapshots');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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_client_snapshots', function (Blueprint $table) {
|
||||
// Unifi reports signal strength in dBm (typically -30 to -95, negative).
|
||||
// SMALLINT so a nullable signed value fits comfortably.
|
||||
$table->smallInteger('signal')->nullable()->after('satisfaction');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('unifi_client_snapshots', function (Blueprint $table) {
|
||||
$table->dropColumn('signal');
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user