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>
80 lines
2.3 KiB
PHP
80 lines
2.3 KiB
PHP
<?php
|
|
|
|
namespace Dashboard\Unifi\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
class UnifiCronRun extends Model
|
|
{
|
|
protected $table = 'unifi_cron_runs';
|
|
|
|
public $timestamps = false;
|
|
|
|
protected $fillable = [
|
|
'command',
|
|
'triggered_by',
|
|
'triggered_by_user_id',
|
|
'started_at',
|
|
'finished_at',
|
|
'status',
|
|
'details',
|
|
];
|
|
|
|
protected $casts = [
|
|
'started_at' => 'datetime',
|
|
'finished_at' => 'datetime',
|
|
'details' => 'array',
|
|
];
|
|
|
|
public function triggeredByUser()
|
|
{
|
|
return $this->belongsTo(\App\Models\User::class, 'triggered_by_user_id');
|
|
}
|
|
|
|
/**
|
|
* Wraps a unit of cron work, recording start/finish/status and any
|
|
* exception. Returns whatever the work returns; the resulting
|
|
* UnifiCronRun row is returned via the $run reference param.
|
|
*/
|
|
public static function record(string $command, string $triggeredBy, ?int $userId, callable $work): self
|
|
{
|
|
$run = static::create([
|
|
'command' => $command,
|
|
'triggered_by' => $triggeredBy,
|
|
'triggered_by_user_id' => $userId,
|
|
'started_at' => now(),
|
|
'status' => 'running',
|
|
]);
|
|
|
|
try {
|
|
$details = $work($run);
|
|
|
|
// Caller can return a status string ("skipped", "partial",
|
|
// etc.) by sticking it under the 'status' key in details.
|
|
// Default = succeeded.
|
|
$status = is_array($details) && isset($details['status'])
|
|
? $details['status']
|
|
: 'succeeded';
|
|
|
|
$run->update([
|
|
'finished_at' => now(),
|
|
'status' => $status,
|
|
'details' => is_array($details) ? array_diff_key($details, ['status' => null]) : null,
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
$run->update([
|
|
'finished_at' => now(),
|
|
'status' => 'failed',
|
|
'details' => [
|
|
'error' => $e->getMessage(),
|
|
'class' => $e::class,
|
|
'file' => $e->getFile() . ':' . $e->getLine(),
|
|
],
|
|
]);
|
|
throw $e;
|
|
}
|
|
|
|
return $run->refresh();
|
|
}
|
|
}
|