Files
central-unifi/src/Console/SyncPpskSchedules.php
jwed fc4f5370ae fix(ppsk): null schedule = always on; disabled global toggle restores all
Previously SyncPpskSchedules returned early when the global setting
was disabled, leaving any PPSK that had been held by a prior sync
stuck in 'held' state. It also only iterated whereNotNull(schedule),
so null-schedule PPSKs ("always on") were never drift-corrected back
to active either.

Now the command always runs and computes a per-PPSK target state:
  - global ppsk_scheduling disabled  → target = active (always)
  - global enabled  + null schedule  → target = active (always)
  - global enabled  + has schedule   → follow the schedule's slot

PPSKs that drift from the target get enabled/disabled accordingly.
Schedules in unifi_ppsks.schedule are preserved across global toggles
either way — disabling the setting doesn't touch them, so re-enabling
resumes the operator's per-PPSK schedules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 16:09:40 -04:00

105 lines
3.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Dashboard\Unifi\Console;
use App\Models\Setting;
use Dashboard\Unifi\Models\UnifiPpsk;
use Dashboard\Unifi\Services\UnifiApiClient;
use Illuminate\Console\Command;
class SyncPpskSchedules extends Command
{
protected $signature = 'unifi:sync-ppsk-schedules {--force : Run even if PPSK scheduling is disabled}';
protected $description = 'Enable or disable PPSKs based on their weekly half-hour schedule, kicking active clients when disabling';
public function handle(UnifiApiClient $unifi): int
{
// Always run, even when global ppsk_scheduling is disabled — in
// that case the target state for every PPSK is "active" (always
// on). That way disabling the global setting actually restores
// any held PPSKs to active without operators having to do
// anything else, and null-schedule PPSKs always end up active.
// Schedules in the DB are preserved regardless of toggle state,
// so re-enabling resumes the per-PPSK schedule.
$globalEnabled = (bool) Setting::get('unifi.ppsk_scheduling.enabled');
$tz = \App\Support\Timezone::current();
$now = now($tz);
$day = $now->dayOfWeek; // 0=Sun … 6=Sat
$slot = $now->hour * 2 + ($now->minute >= 30 ? 1 : 0); // 0–47
$ppsks = UnifiPpsk::all();
if ($ppsks->isEmpty()) {
return self::SUCCESS;
}
// Fetch network confs once so we can resolve vlan → networkconf_id on re-enable
$networksByVlan = [];
try {
foreach ($unifi->getNetworkConfs() as $n) {
if (isset($n['vlan'])) {
$networksByVlan[(int) $n['vlan']] = $n;
}
}
} catch (\Throwable $e) {
$this->warn("Could not fetch network configs: {$e->getMessage()}");
}
foreach ($ppsks as $ppsk) {
// Default to "always on". Only consult the schedule if
// global scheduling is enabled AND this PPSK has one.
$shouldBeOn = true;
if ($globalEnabled && $ppsk->schedule) {
$shouldBeOn = (bool) ($ppsk->schedule[$day * 48 + $slot] ?? true);
}
if ($shouldBeOn && $ppsk->state === 'held') {
$this->enablePpsk($ppsk, $unifi, $networksByVlan);
} elseif (! $shouldBeOn && $ppsk->state === 'active' && $ppsk->unifi_id) {
$this->disablePpsk($ppsk, $unifi);
}
}
return self::SUCCESS;
}
private function enablePpsk(UnifiPpsk $ppsk, UnifiApiClient $unifi, array $networksByVlan): void
{
try {
$data = [
'name' => $ppsk->name,
'x_passphrase' => $ppsk->x_passphrase,
'wlan_id' => $ppsk->wlan_id,
];
if ($ppsk->vlan && isset($networksByVlan[$ppsk->vlan])) {
$data['networkconf_id'] = $networksByVlan[$ppsk->vlan]['_id'];
}
$result = $unifi->createPpsk($data);
$raw = $result[0] ?? $result;
$newId = $raw['_id'] ?? null;
$ppsk->update(['state' => 'active', 'unifi_id' => $newId]);
$this->info("Enabled: {$ppsk->name} (wlan {$ppsk->wlan_id})");
} catch (\Throwable $e) {
$this->error("Failed to enable PPSK \"{$ppsk->name}\": {$e->getMessage()}");
}
}
private function disablePpsk(UnifiPpsk $ppsk, UnifiApiClient $unifi): void
{
try {
$kicked = $unifi->kickClientsForPpsk($ppsk->unifi_id);
$unifi->deletePpsk($ppsk->unifi_id);
$ppsk->update(['state' => 'held', 'unifi_id' => null]);
$suffix = $kicked > 0 ? " — kicked {$kicked} client(s)" : '';
$this->info("Disabled: {$ppsk->name}{$suffix}");
} catch (\Throwable $e) {
$this->error("Failed to disable PPSK \"{$ppsk->name}\": {$e->getMessage()}");
}
}
}