From 070001ffff001a47a690906a0de1880f3627c383 Mon Sep 17 00:00:00 2001 From: jwed Date: Sun, 19 Jul 2026 19:37:44 -0400 Subject: [PATCH] Guest Wi-Fi dashboard card (network + rotating password + join QR) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New home card 'unifi.guest_wifi': shows the guest SSID, the current rotated password (unifi.password_rotation.last_password), and a WPA join QR built from the WIFI: string. Polls app/unifi/guest-card/data (auth) every 60s so it updates the moment the rotation engine changes the password — the QR regenerates client-side to match. Self-hides until enabled with an SSID and a rotated password; grant-gated via Admin -> Settings -> Dashboard Cards. New settings (WiFi rotation section): enable + network name + card title. Co-Authored-By: Claude Fable 5 --- composer.json | 2 +- src/Http/Controllers/GuestCardController.php | 65 +++++++++++++++++++ .../Controllers/UnifiSettingsController.php | 9 +++ src/UnifiServiceProvider.php | 16 +++++ src/routes/unifi.php | 7 ++ 5 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/Http/Controllers/GuestCardController.php diff --git a/composer.json b/composer.json index 0486932..57e378b 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "dashboard/unifi", "description": "UniFi network management, WiFi stats, and captive portal authentication for the Dashboard platform", - "version": "1.13.1", + "version": "1.13.2", "type": "library", "license": "MIT", "autoload": { diff --git a/src/Http/Controllers/GuestCardController.php b/src/Http/Controllers/GuestCardController.php new file mode 100644 index 0000000..187841b --- /dev/null +++ b/src/Http/Controllers/GuestCardController.php @@ -0,0 +1,65 @@ + trim((string) Setting::get('unifi.guest_card.title', '')) ?: 'Guest Wi-Fi', + 'ssid' => $ssid, + 'password' => $password, + 'rotated_at' => Setting::get('unifi.password_rotation.last_rotated_at'), + // WPA/WPA2 join string a phone camera understands. Special + // chars in SSID/password must be backslash-escaped per the + // Wi-Fi QR spec. + 'qr' => self::wifiQrString($ssid, $password), + 'frequency' => in_array($freq, ['daily', 'weekly'], true) ? $freq : 'weekly', + ]; + } + + /** Live poll for the card (any logged-in user who can see the card). */ + public function data() + { + return response()->json(self::payload() ?? ['title' => null]); + } + + /** Card registry resolver. */ + public static function resolve(User $user): ?array + { + return self::payload(); + } + + private static function wifiQrString(string $ssid, string $password): string + { + $esc = fn (string $v) => preg_replace('/([\\\\;,:"])/', '\\\\$1', $v); + + return sprintf('WIFI:T:WPA;S:%s;P:%s;;', $esc($ssid), $esc($password)); + } +} diff --git a/src/Http/Controllers/UnifiSettingsController.php b/src/Http/Controllers/UnifiSettingsController.php index 31ebe07..82a78d5 100644 --- a/src/Http/Controllers/UnifiSettingsController.php +++ b/src/Http/Controllers/UnifiSettingsController.php @@ -36,6 +36,9 @@ class UnifiSettingsController extends Controller 'ppskSchedulingEnabled' => (bool) Setting::get('unifi.ppsk_scheduling.enabled', false), 'apiEnabled' => (bool) Setting::get('unifi.api.enabled', false), 'apiToken' => Setting::get('unifi.api_token', null), + 'guestCardEnabled' => (bool) Setting::get('unifi.guest_card.enabled', false), + 'guestCardSsid' => Setting::get('unifi.guest_card.ssid', ''), + 'guestCardTitle' => Setting::get('unifi.guest_card.title', ''), ]); } @@ -74,6 +77,9 @@ class UnifiSettingsController extends Controller 'rotation_wordlist' => 'nullable|string|max:20000', 'ppsk_scheduling_enabled' => 'boolean', 'api_enabled' => 'boolean', + 'guest_card_enabled' => 'boolean', + 'guest_card_ssid' => 'nullable|string|max:100', + 'guest_card_title' => 'nullable|string|max:100', ]); Setting::set('unifi.controller_url', rtrim($request->controller_url, '/')); @@ -101,6 +107,9 @@ class UnifiSettingsController extends Controller Setting::set('unifi.password_rotation.wordlist', $request->input('rotation_wordlist', '')); Setting::set('unifi.ppsk_scheduling.enabled', $request->boolean('ppsk_scheduling_enabled') ? '1' : ''); Setting::set('unifi.api.enabled', $request->boolean('api_enabled') ? '1' : ''); + Setting::set('unifi.guest_card.enabled', $request->boolean('guest_card_enabled') ? '1' : ''); + Setting::set('unifi.guest_card.ssid', trim((string) $request->input('guest_card_ssid', ''))); + Setting::set('unifi.guest_card.title', trim((string) $request->input('guest_card_title', ''))); \Illuminate\Support\Facades\Cache::forget('unifi:api_prefix:' . md5(rtrim($request->controller_url, '/'))); diff --git a/src/UnifiServiceProvider.php b/src/UnifiServiceProvider.php index 18cd102..00deb6d 100644 --- a/src/UnifiServiceProvider.php +++ b/src/UnifiServiceProvider.php @@ -24,6 +24,22 @@ class UnifiServiceProvider extends ServiceProvider $this->loadRoutesFrom(__DIR__ . '/routes/unifi.php'); $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); + // "/" home card: guest Wi-Fi network + current rotating password + + // QR (re-renders when the password changes). Grant-gated via + // Admin → Settings → Dashboard Cards; self-hides until enabled and + // a password has been rotated. + try { + app(\App\Support\DashboardCardRegistry::class)->register( + 'unifi.guest_wifi', + 'Guest Wi-Fi', + 'UnifiGuestWifi', + fn (\App\Models\User $user) => \Dashboard\Unifi\Http\Controllers\GuestCardController::resolve($user), + 'The guest network name, current password, and a join QR code — updates automatically when the password rotates.', + ); + } catch (\Throwable) { + // Older shell without the card registry — skip. + } + // Tell the shell's nav sidebar which unifi nav items the user // can see: pages whose required_permission the user holds through // groups, plus pages granted via the Settings → Access tab. The diff --git a/src/routes/unifi.php b/src/routes/unifi.php index e6a929f..0ec716e 100644 --- a/src/routes/unifi.php +++ b/src/routes/unifi.php @@ -103,6 +103,13 @@ Route::middleware(['web', 'auth', 'app.access:unifi']) }); }); +// ── Guest Wi-Fi home-card poll (any logged-in user; card visibility is +// grant-gated by the shell). Exempt from the unifi page-grant middleware +// because it's a home-page widget, not a unifi page. ── +Route::middleware(['web', 'auth']) + ->get('/app/unifi/guest-card/data', [\Dashboard\Unifi\Http\Controllers\GuestCardController::class, 'data']) + ->name('unifi.guest-card.data'); + // ── Public API (token-protected) ────────────────────────────────────────── // External integrations (signage, kiosks) hit these without session auth. Route::prefix('api/unifi')->name('unifi.api.')->group(function () {