feat(logs): structured cron run history + read endpoint

Adds unifi_cron_runs table (one row per scheduled-task execution) and
UnifiCronRun::record() wrapper that captures start/finish/status and
exceptions. The three scheduled commands now write through it:

  - reboot-all-aps    → rebooted/failed AP names per run
  - rotate-passwords  → rotated SSIDs + PPSKs, failures (when actually
                        rotating; the "is it due" early-return is silent
                        so we don't flood the log with no-op rows every
                        minute)
  - sync-ppsk-schedules → enabled/disabled PPSKs (silent when there's
                          no work)

UnifiCronLogsController returns the most-recent 200 runs as JSON,
filterable by command + status. Behind permission:unifi.settings; no
super-admin required — read-only history is fine for any operator
who can see settings.

v1.5.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 16:05:36 -04:00
parent a33f2885ff
commit 75943fbe2b
8 changed files with 325 additions and 125 deletions

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Structured log of every unifi scheduled-task execution: AP reboots,
* password rotations, PPSK schedule syncs. One row per run.
* Surfaced in the Logs tab of the Unifi settings page.
*/
public function up(): void
{
Schema::create('unifi_cron_runs', function (Blueprint $table) {
$table->id();
$table->string('command', 64)->index(); // 'reboot-all-aps' | 'rotate-passwords' | 'sync-ppsk-schedules'
$table->enum('triggered_by', ['schedule', 'manual']);
$table->foreignId('triggered_by_user_id')->nullable()->constrained('users')->nullOnDelete();
$table->timestamp('started_at')->index();
$table->timestamp('finished_at')->nullable();
$table->string('status', 16); // 'running' | 'succeeded' | 'partial' | 'failed' | 'skipped'
$table->longText('details')->nullable(); // JSON: counts, per-item actions, error summary
});
}
public function down(): void
{
Schema::dropIfExists('unifi_cron_runs');
}
};