chore: remove AP Groups surfaces (legacy API auth incompatible)
UniFi's /rest/apgroup endpoints (and per-SSID ap_group_ids writes via
/rest/wlanconf) require session-cookie auth — they don't accept the
X-API-Key header. The Integration API doesn't expose AP groups at all.
So with the current deployment running on API-key auth, every AP-group
operation returned 400 api.err.InvalidObject. Removing the dead code
rather than carrying a feature that can't function.
* Deleted ApGroupController, ApGroups.vue, the /ap-groups/* routes,
and getApGroups/createApGroup/updateApGroup/deleteApGroup from
UnifiApiClient.
* Removed the per-SSID AP-group assignment from Wifi.vue + the
updateApGroups action + /wifi/{wlanId}/ap-groups route + the
ap_group_ids field from the mapWlan output.
* Removed the AP Groups nav entry from composer.json.
If a future deploy adds local-admin username+password auth, AP groups
can be reintroduced — the UnifiApiClient::buildRequest() session-cookie
path is intact.
v1.3.1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "dashboard/unifi",
|
"name": "dashboard/unifi",
|
||||||
"description": "UniFi network management, WiFi stats, and captive portal authentication for the Dashboard platform",
|
"description": "UniFi network management, WiFi stats, and captive portal authentication for the Dashboard platform",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -27,9 +27,8 @@
|
|||||||
{ "label": "Devices", "route_name": "unifi.devices", "icon": "cpu-chip", "permission": "unifi.stats", "sort_order": 3 },
|
{ "label": "Devices", "route_name": "unifi.devices", "icon": "cpu-chip", "permission": "unifi.stats", "sort_order": 3 },
|
||||||
{ "label": "Clients", "route_name": "unifi.clients", "icon": "users", "permission": "unifi.stats", "sort_order": 4 },
|
{ "label": "Clients", "route_name": "unifi.clients", "icon": "users", "permission": "unifi.stats", "sort_order": 4 },
|
||||||
{ "label": "WiFi Networks", "route_name": "unifi.wifi", "icon": "wifi", "permission": "unifi.manage", "sort_order": 5 },
|
{ "label": "WiFi Networks", "route_name": "unifi.wifi", "icon": "wifi", "permission": "unifi.manage", "sort_order": 5 },
|
||||||
{ "label": "AP Groups", "route_name": "unifi.ap-groups.index", "icon": "rectangle-stack", "permission": "unifi.manage", "sort_order": 6 },
|
{ "label": "Portal", "route_name": "unifi.portal.settings", "icon": "shield-check", "permission": "unifi.auth", "sort_order": 6 },
|
||||||
{ "label": "Portal", "route_name": "unifi.portal.settings", "icon": "shield-check", "permission": "unifi.auth", "sort_order": 7 },
|
{ "label": "Webhooks", "route_name": "unifi.webhooks.index", "icon": "bell-alert", "permission": "unifi.settings", "sort_order": 7 },
|
||||||
{ "label": "Webhooks", "route_name": "unifi.webhooks.index", "icon": "bell-alert", "permission": "unifi.settings", "sort_order": 8 },
|
|
||||||
{ "label": "Settings", "route_name": "unifi.settings", "icon": "cog-6-tooth", "permission": "unifi.settings", "sort_order": 99 }
|
{ "label": "Settings", "route_name": "unifi.settings", "icon": "cog-6-tooth", "permission": "unifi.settings", "sort_order": 99 }
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Dashboard\Unifi\Http\Controllers;
|
|
||||||
|
|
||||||
use Dashboard\Unifi\Services\UnifiApiClient;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Routing\Controller;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
|
|
||||||
class ApGroupController extends Controller
|
|
||||||
{
|
|
||||||
public function index(UnifiApiClient $unifi)
|
|
||||||
{
|
|
||||||
// Always pull fresh from the controller on this page so the
|
|
||||||
// operator never edits against a stale snapshot. getApGroups()
|
|
||||||
// and getWlans() aren't cached, but getDevices() (which feeds
|
|
||||||
// the AP picker) is — bust it explicitly.
|
|
||||||
Cache::forget('unifi:devices');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$groups = collect($unifi->getApGroups())->map(fn ($g) => [
|
|
||||||
'id' => $g['_id'],
|
|
||||||
'name' => $g['name'] ?? 'Unnamed',
|
|
||||||
'device_macs' => $g['device_macs'] ?? [],
|
|
||||||
'is_default' => $g['attr_no_delete'] ?? false,
|
|
||||||
])->values();
|
|
||||||
|
|
||||||
$devices = collect($unifi->getAccessPoints())->map(fn ($d) => [
|
|
||||||
'mac' => strtolower($d['mac']),
|
|
||||||
'name' => $d['name'] ?? $d['model'] ?? $d['mac'],
|
|
||||||
'model' => $d['model'] ?? '',
|
|
||||||
'state' => $d['state'] ?? 0,
|
|
||||||
])->values();
|
|
||||||
|
|
||||||
return Inertia::render('Unifi/ApGroups', [
|
|
||||||
'groups' => $groups,
|
|
||||||
'devices' => $devices,
|
|
||||||
]);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return Inertia::render('Unifi/ApGroups', [
|
|
||||||
'groups' => [], 'devices' => [], 'error' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function store(Request $request, UnifiApiClient $unifi)
|
|
||||||
{
|
|
||||||
$data = $request->validate([
|
|
||||||
'name' => 'required|string|max:100',
|
|
||||||
'device_macs' => 'present|array',
|
|
||||||
'device_macs.*' => 'string',
|
|
||||||
]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$result = $unifi->createApGroup([
|
|
||||||
'name' => $data['name'],
|
|
||||||
'device_macs' => array_values(array_map('strtolower', $data['device_macs'])),
|
|
||||||
]);
|
|
||||||
return back()->with('success', 'AP group created.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return back()->withErrors(['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(Request $request, string $groupId, UnifiApiClient $unifi)
|
|
||||||
{
|
|
||||||
$data = $request->validate([
|
|
||||||
'name' => 'sometimes|string|max:100',
|
|
||||||
'device_macs' => 'sometimes|array',
|
|
||||||
'device_macs.*' => 'string',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (isset($data['device_macs'])) {
|
|
||||||
$data['device_macs'] = array_values(array_map('strtolower', $data['device_macs']));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$unifi->updateApGroup($groupId, $data);
|
|
||||||
return back()->with('success', 'AP group updated.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return back()->withErrors(['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function destroy(string $groupId, UnifiApiClient $unifi)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$unifi->deleteApGroup($groupId);
|
|
||||||
return back()->with('success', 'AP group deleted.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return back()->withErrors(['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,25 +13,9 @@ class WifiController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(UnifiApiClient $unifi)
|
public function index(UnifiApiClient $unifi)
|
||||||
{
|
{
|
||||||
// Always pull fresh device data on this page so AP-group / SSID
|
|
||||||
// edits never go out against a stale snapshot. getWlans() and
|
|
||||||
// getApGroups() aren't cached, but getDevices() is.
|
|
||||||
\Illuminate\Support\Facades\Cache::forget('unifi:devices');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$wlans = collect($unifi->getWlans())->map(fn ($w) => $this->mapWlan($w))->values();
|
$wlans = collect($unifi->getWlans())->map(fn ($w) => $this->mapWlan($w))->values();
|
||||||
|
|
||||||
try {
|
|
||||||
$apGroups = collect($unifi->getApGroups())->map(fn ($g) => [
|
|
||||||
'id' => $g['_id'],
|
|
||||||
'name' => $g['attr_no_delete'] ?? false ? 'Default' : ($g['name'] ?? 'Unnamed'),
|
|
||||||
'device_macs' => $g['device_macs'] ?? [],
|
|
||||||
'is_default' => $g['attr_no_delete'] ?? false,
|
|
||||||
])->values();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$apGroups = collect(); // AP groups not supported by this controller
|
|
||||||
}
|
|
||||||
|
|
||||||
$raw = Setting::get('unifi.ssid_groups', '{}');
|
$raw = Setting::get('unifi.ssid_groups', '{}');
|
||||||
$groups = json_decode($raw, true);
|
$groups = json_decode($raw, true);
|
||||||
if (! is_array($groups) || array_is_list($groups)) $groups = [];
|
if (! is_array($groups) || array_is_list($groups)) $groups = [];
|
||||||
@@ -42,13 +26,12 @@ class WifiController extends Controller
|
|||||||
return Inertia::render('Unifi/Wifi', [
|
return Inertia::render('Unifi/Wifi', [
|
||||||
'wlans' => $wlans,
|
'wlans' => $wlans,
|
||||||
'groups' => $groups,
|
'groups' => $groups,
|
||||||
'apGroups' => $apGroups,
|
|
||||||
'rotateWlanIds' => $rotateWlanIds,
|
'rotateWlanIds' => $rotateWlanIds,
|
||||||
'ppskSchedulingEnabled' => (bool) Setting::get('unifi.ppsk_scheduling.enabled', false),
|
'ppskSchedulingEnabled' => (bool) Setting::get('unifi.ppsk_scheduling.enabled', false),
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return Inertia::render('Unifi/Wifi', [
|
return Inertia::render('Unifi/Wifi', [
|
||||||
'wlans' => [], 'groups' => [], 'apGroups' => [], 'rotateWlanIds' => [], 'error' => $e->getMessage(),
|
'wlans' => [], 'groups' => [], 'rotateWlanIds' => [], 'error' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,21 +86,6 @@ class WifiController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update AP group assignments for a single WLAN (not synced to group siblings).
|
|
||||||
*/
|
|
||||||
public function updateApGroups(Request $request, string $wlanId, UnifiApiClient $unifi)
|
|
||||||
{
|
|
||||||
$request->validate(['ap_group_ids' => 'required|array']);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$unifi->updateWlan($wlanId, ['ap_group_ids' => $request->ap_group_ids]);
|
|
||||||
return back()->with('success', 'AP groups updated.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return back()->withErrors(['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toggle(Request $request, string $wlanId, UnifiApiClient $unifi)
|
public function toggle(Request $request, string $wlanId, UnifiApiClient $unifi)
|
||||||
{
|
{
|
||||||
$request->validate(['enabled' => 'required|boolean']);
|
$request->validate(['enabled' => 'required|boolean']);
|
||||||
@@ -419,7 +387,6 @@ class WifiController extends Controller
|
|||||||
'hide_ssid' => $w['hide_ssid'] ?? false,
|
'hide_ssid' => $w['hide_ssid'] ?? false,
|
||||||
'passphrase' => $w['x_passphrase'] ?? '',
|
'passphrase' => $w['x_passphrase'] ?? '',
|
||||||
'band' => $this->detectBand($w),
|
'band' => $this->detectBand($w),
|
||||||
'ap_group_ids' => $w['ap_group_ids'] ?? [],
|
|
||||||
'mac_filter_enabled' => $w['mac_filter_enabled'] ?? false,
|
'mac_filter_enabled' => $w['mac_filter_enabled'] ?? false,
|
||||||
'mac_filter_policy' => $w['mac_filter_policy'] ?? 'deny',
|
'mac_filter_policy' => $w['mac_filter_policy'] ?? 'deny',
|
||||||
'ppsk_enabled' => ($w['wpa3_ppsk'] ?? false)
|
'ppsk_enabled' => ($w['wpa3_ppsk'] ?? false)
|
||||||
|
|||||||
@@ -312,28 +312,6 @@ class UnifiApiClient
|
|||||||
return $this->put("/rest/wlanconf/{$wlanId}", $data);
|
return $this->put("/rest/wlanconf/{$wlanId}", $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── AP Groups ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public function getApGroups(): array
|
|
||||||
{
|
|
||||||
return $this->get('/rest/apgroups');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createApGroup(array $data): array
|
|
||||||
{
|
|
||||||
return $this->post('/rest/apgroups', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateApGroup(string $groupId, array $data): array
|
|
||||||
{
|
|
||||||
return $this->put("/rest/apgroups/{$groupId}", $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteApGroup(string $groupId): void
|
|
||||||
{
|
|
||||||
$this->delete("/rest/apgroups/{$groupId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── PPSK ─────────────────────────────────────────────────────────────────
|
// ── PPSK ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Dashboard\Unifi\Http\Controllers\ApGroupController;
|
|
||||||
use Dashboard\Unifi\Http\Controllers\ClientController;
|
use Dashboard\Unifi\Http\Controllers\ClientController;
|
||||||
use Dashboard\Unifi\Http\Controllers\DeviceController;
|
use Dashboard\Unifi\Http\Controllers\DeviceController;
|
||||||
use Dashboard\Unifi\Http\Controllers\PortalController;
|
use Dashboard\Unifi\Http\Controllers\PortalController;
|
||||||
@@ -32,7 +31,6 @@ Route::middleware(['web', 'auth', 'app.access:unifi'])
|
|||||||
// WiFi networks
|
// WiFi networks
|
||||||
Route::get('/wifi', [WifiController::class, 'index']) ->name('wifi');
|
Route::get('/wifi', [WifiController::class, 'index']) ->name('wifi');
|
||||||
Route::put('/wifi/{wlanId}', [WifiController::class, 'update']) ->name('wifi.update');
|
Route::put('/wifi/{wlanId}', [WifiController::class, 'update']) ->name('wifi.update');
|
||||||
Route::put('/wifi/{wlanId}/ap-groups', [WifiController::class, 'updateApGroups']) ->name('wifi.ap-groups');
|
|
||||||
Route::post('/wifi/{wlanId}/toggle', [WifiController::class, 'toggle']) ->name('wifi.toggle');
|
Route::post('/wifi/{wlanId}/toggle', [WifiController::class, 'toggle']) ->name('wifi.toggle');
|
||||||
Route::post('/wifi/groups', [WifiController::class, 'saveGroups']) ->name('wifi.groups');
|
Route::post('/wifi/groups', [WifiController::class, 'saveGroups']) ->name('wifi.groups');
|
||||||
|
|
||||||
@@ -44,12 +42,6 @@ Route::middleware(['web', 'auth', 'app.access:unifi'])
|
|||||||
Route::put('/wifi/{wlanId}/ppsk/{ppskId}/schedule', [WifiController::class, 'ppskSchedule']) ->name('wifi.ppsk.schedule');
|
Route::put('/wifi/{wlanId}/ppsk/{ppskId}/schedule', [WifiController::class, 'ppskSchedule']) ->name('wifi.ppsk.schedule');
|
||||||
Route::patch('/wifi/{wlanId}/ppsk/{ppskId}/rotation',[WifiController::class, 'ppskToggleRotation'])->name('wifi.ppsk.rotation');
|
Route::patch('/wifi/{wlanId}/ppsk/{ppskId}/rotation',[WifiController::class, 'ppskToggleRotation'])->name('wifi.ppsk.rotation');
|
||||||
|
|
||||||
// AP Groups
|
|
||||||
Route::get('/ap-groups', [ApGroupController::class, 'index']) ->name('ap-groups.index');
|
|
||||||
Route::post('/ap-groups', [ApGroupController::class, 'store']) ->name('ap-groups.store');
|
|
||||||
Route::put('/ap-groups/{groupId}', [ApGroupController::class, 'update']) ->name('ap-groups.update');
|
|
||||||
Route::delete('/ap-groups/{groupId}', [ApGroupController::class, 'destroy']) ->name('ap-groups.destroy');
|
|
||||||
|
|
||||||
// Devices
|
// Devices
|
||||||
Route::post('/devices/reboot', [DeviceController::class, 'reboot']) ->name('devices.reboot');
|
Route::post('/devices/reboot', [DeviceController::class, 'reboot']) ->name('devices.reboot');
|
||||||
Route::post('/clients/kick', [ClientController::class, 'kick']) ->name('clients.kick');
|
Route::post('/clients/kick', [ClientController::class, 'kick']) ->name('clients.kick');
|
||||||
|
|||||||
Reference in New Issue
Block a user