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>
70 lines
2.6 KiB
PHP
70 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace Dashboard\Unifi\Console;
|
|
|
|
use Dashboard\Unifi\Models\UnifiCronRun;
|
|
use Dashboard\Unifi\Services\UnifiApiClient;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
class RebootAllAps extends Command
|
|
{
|
|
protected $signature = 'unifi:reboot-all-aps {--delay=5 : Seconds to wait between each reboot} {--triggered-by=schedule}';
|
|
protected $description = 'Planned reboot of all access points — suppresses webhook offline/online alerts';
|
|
|
|
public function handle(UnifiApiClient $unifi): int
|
|
{
|
|
$run = UnifiCronRun::record(
|
|
'reboot-all-aps',
|
|
$this->option('triggered-by') ?: 'schedule',
|
|
null,
|
|
function () use ($unifi) {
|
|
$aps = $unifi->getAccessPoints();
|
|
|
|
if (empty($aps)) {
|
|
$this->warn('No access points found.');
|
|
return ['status' => 'skipped', 'reason' => 'no APs found'];
|
|
}
|
|
|
|
$delay = max(0, (int) $this->option('delay'));
|
|
$rebooted = [];
|
|
$failed = [];
|
|
|
|
foreach ($aps as $ap) {
|
|
$mac = strtolower($ap['mac']);
|
|
Cache::put("unifi:planned_reboot:{$mac}", true, now()->addMinutes(20));
|
|
$this->line("Marked planned reboot: {$ap['name']} ({$mac})");
|
|
}
|
|
$this->newLine();
|
|
|
|
foreach ($aps as $ap) {
|
|
$mac = strtolower($ap['mac']);
|
|
$name = $ap['name'] ?? $mac;
|
|
try {
|
|
$unifi->rebootDevice($mac);
|
|
$this->info("Rebooted: {$name} ({$mac})");
|
|
$rebooted[] = $name;
|
|
} catch (\Throwable $e) {
|
|
$this->error("Failed to reboot {$name}: {$e->getMessage()}");
|
|
$failed[] = ['name' => $name, 'error' => $e->getMessage()];
|
|
}
|
|
|
|
if ($delay > 0 && count($rebooted) + count($failed) < count($aps)) {
|
|
sleep($delay);
|
|
}
|
|
}
|
|
|
|
return [
|
|
'status' => count($failed) === 0 ? 'succeeded' : (count($rebooted) > 0 ? 'partial' : 'failed'),
|
|
'rebooted' => $rebooted,
|
|
'failed' => $failed,
|
|
'total' => count($aps),
|
|
];
|
|
}
|
|
);
|
|
|
|
$this->info("Done. Status: {$run->status}.");
|
|
return $run->status === 'failed' ? self::FAILURE : self::SUCCESS;
|
|
}
|
|
}
|