feat: password rotation, PPSK management, VLAN/AP groups

- Add password rotation: RotatePasswords console command + migration + service updates
- Add PPSK management: UnifiPpsk model, migration, SyncPpskSchedules console
- Add VLAN groups and AP groups: VlanGroupController, ApGroupController, model, migration
- Add RebootAllAps console command
- Add in_alert column to device states
- Wire new features through service provider, routes, and existing controllers/services

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 17:54:24 -04:00
parent ce3217d8f4
commit 0802ef35f3
22 changed files with 1771 additions and 305 deletions

View File

@@ -0,0 +1,30 @@
<?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_password_rotations', function (Blueprint $table) {
$table->id();
$table->string('name'); // display name (e.g. "Staff WiFi")
$table->json('wlan_ids'); // array of WLAN IDs to rotate
$table->text('wordlist')->nullable(); // one password per line
$table->boolean('enabled')->default(true);
$table->string('frequency')->default('weekly'); // daily | weekly
$table->tinyInteger('day_of_week')->nullable(); // 0=Sun ... 6=Sat (weekly only)
$table->tinyInteger('hour')->default(1);
$table->tinyInteger('minute')->default(0);
$table->timestamp('last_rotated_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('unifi_password_rotations');
}
};

View File

@@ -0,0 +1,25 @@
<?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_vlan_groups', function (Blueprint $table) {
$table->id();
$table->string('name'); // e.g. "Students"
$table->unsignedSmallInteger('vlan_id'); // 14094
$table->string('description')->nullable(); // optional note
$table->unsignedSmallInteger('sort_order')->default(0);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('unifi_vlan_groups');
}
};

View File

@@ -0,0 +1,25 @@
<?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) {
// Tracks whether an active device_offline alert has been sent for this device.
// device_online ("resolved") only fires when in_alert = true.
// This prevents orphan "back online" alerts and duplicate offline alerts.
$table->boolean('in_alert')->default(false)->after('was_online');
});
}
public function down(): void
{
Schema::table('unifi_device_states', function (Blueprint $table) {
$table->dropColumn('in_alert');
});
}
};

View File

@@ -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_ppsks', function (Blueprint $table) {
$table->id();
$table->string('wlan_id', 36)->index(); // UniFi WLAN _id
$table->string('unifi_id', 36)->nullable()->index(); // null when held (not currently in UniFi)
$table->string('name', 100);
$table->string('x_passphrase', 63);
$table->unsignedSmallInteger('vlan')->nullable();
$table->enum('state', ['active', 'held'])->default('active')->index();
$table->boolean('rotate_password')->default(false);
$table->json('schedule')->nullable(); // 336 booleans [day*48+slot], null = unscheduled
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('unifi_ppsks');
}
};