feat: add wireless-issues API endpoints for dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -413,3 +413,159 @@ async def fleet_summary_all(
|
|||||||
result = await db.execute(text(_FLEET_SUMMARY_SQL))
|
result = await db.execute(text(_FLEET_SUMMARY_SQL))
|
||||||
rows = result.mappings().all()
|
rows = result.mappings().all()
|
||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Wireless issues (dashboard)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tenants/{tenant_id}/fleet/wireless-issues")
|
||||||
|
async def get_wireless_issues(
|
||||||
|
tenant_id: uuid.UUID,
|
||||||
|
current_user: CurrentUser = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Get wireless APs with degraded performance for a tenant."""
|
||||||
|
await _check_tenant_access(current_user, tenant_id, db)
|
||||||
|
|
||||||
|
result = await db.execute(
|
||||||
|
text("""
|
||||||
|
WITH latest_wireless AS (
|
||||||
|
SELECT DISTINCT ON (device_id, interface)
|
||||||
|
device_id, interface, client_count, avg_signal, ccq, frequency, time
|
||||||
|
FROM wireless_metrics
|
||||||
|
WHERE tenant_id = :tenant_id
|
||||||
|
AND time > NOW() - INTERVAL '1 hour'
|
||||||
|
ORDER BY device_id, interface, time DESC
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
lw.device_id,
|
||||||
|
d.hostname,
|
||||||
|
lw.interface,
|
||||||
|
lw.avg_signal,
|
||||||
|
lw.ccq,
|
||||||
|
lw.client_count,
|
||||||
|
lw.frequency
|
||||||
|
FROM latest_wireless lw
|
||||||
|
JOIN devices d ON d.id = lw.device_id
|
||||||
|
WHERE lw.avg_signal < -70
|
||||||
|
OR lw.ccq < 60
|
||||||
|
OR (lw.client_count = 0 AND EXISTS (
|
||||||
|
SELECT 1 FROM wireless_metrics wm
|
||||||
|
WHERE wm.device_id = lw.device_id
|
||||||
|
AND wm.interface = lw.interface
|
||||||
|
AND wm.client_count > 0
|
||||||
|
AND wm.time > NOW() - INTERVAL '24 hours'
|
||||||
|
AND wm.tenant_id = :tenant_id
|
||||||
|
))
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN d.status = 'offline' THEN 0 ELSE 1 END,
|
||||||
|
lw.avg_signal ASC,
|
||||||
|
lw.ccq ASC
|
||||||
|
LIMIT 10
|
||||||
|
"""),
|
||||||
|
{"tenant_id": str(tenant_id)},
|
||||||
|
)
|
||||||
|
|
||||||
|
rows = result.fetchall()
|
||||||
|
issues = []
|
||||||
|
for row in rows:
|
||||||
|
if row.avg_signal is not None and row.avg_signal < -70:
|
||||||
|
issue = f"Signal: {row.avg_signal} dBm"
|
||||||
|
elif row.ccq is not None and row.ccq < 60:
|
||||||
|
issue = f"CCQ: {row.ccq}%"
|
||||||
|
elif row.client_count == 0:
|
||||||
|
issue = "No clients (was active)"
|
||||||
|
else:
|
||||||
|
issue = "Degraded"
|
||||||
|
|
||||||
|
issues.append({
|
||||||
|
"device_id": str(row.device_id),
|
||||||
|
"hostname": row.hostname,
|
||||||
|
"interface": row.interface,
|
||||||
|
"issue": issue,
|
||||||
|
"signal": row.avg_signal,
|
||||||
|
"ccq": row.ccq,
|
||||||
|
"client_count": row.client_count,
|
||||||
|
"frequency": row.frequency,
|
||||||
|
})
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/fleet/wireless-issues")
|
||||||
|
async def get_fleet_wireless_issues(
|
||||||
|
current_user: CurrentUser = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Get wireless APs with issues across all tenants (super_admin only)."""
|
||||||
|
if current_user.role != "super_admin":
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Super admin required",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await db.execute(
|
||||||
|
text("""
|
||||||
|
WITH latest_wireless AS (
|
||||||
|
SELECT DISTINCT ON (device_id, interface)
|
||||||
|
device_id, tenant_id, interface, client_count, avg_signal, ccq, frequency, time
|
||||||
|
FROM wireless_metrics
|
||||||
|
WHERE time > NOW() - INTERVAL '1 hour'
|
||||||
|
ORDER BY device_id, interface, time DESC
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
lw.device_id,
|
||||||
|
d.hostname,
|
||||||
|
t.name as tenant_name,
|
||||||
|
lw.interface,
|
||||||
|
lw.avg_signal,
|
||||||
|
lw.ccq,
|
||||||
|
lw.client_count,
|
||||||
|
lw.frequency
|
||||||
|
FROM latest_wireless lw
|
||||||
|
JOIN devices d ON d.id = lw.device_id
|
||||||
|
JOIN tenants t ON t.id = lw.tenant_id
|
||||||
|
WHERE lw.avg_signal < -70
|
||||||
|
OR lw.ccq < 60
|
||||||
|
OR (lw.client_count = 0 AND EXISTS (
|
||||||
|
SELECT 1 FROM wireless_metrics wm
|
||||||
|
WHERE wm.device_id = lw.device_id
|
||||||
|
AND wm.interface = lw.interface
|
||||||
|
AND wm.client_count > 0
|
||||||
|
AND wm.time > NOW() - INTERVAL '24 hours'
|
||||||
|
))
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN d.status = 'offline' THEN 0 ELSE 1 END,
|
||||||
|
lw.avg_signal ASC,
|
||||||
|
lw.ccq ASC
|
||||||
|
LIMIT 10
|
||||||
|
"""),
|
||||||
|
)
|
||||||
|
|
||||||
|
rows = result.fetchall()
|
||||||
|
issues = []
|
||||||
|
for row in rows:
|
||||||
|
if row.avg_signal is not None and row.avg_signal < -70:
|
||||||
|
issue = f"Signal: {row.avg_signal} dBm"
|
||||||
|
elif row.ccq is not None and row.ccq < 60:
|
||||||
|
issue = f"CCQ: {row.ccq}%"
|
||||||
|
elif row.client_count == 0:
|
||||||
|
issue = "No clients (was active)"
|
||||||
|
else:
|
||||||
|
issue = "Degraded"
|
||||||
|
|
||||||
|
issues.append({
|
||||||
|
"device_id": str(row.device_id),
|
||||||
|
"hostname": row.hostname,
|
||||||
|
"tenant_name": row.tenant_name,
|
||||||
|
"interface": row.interface,
|
||||||
|
"issue": issue,
|
||||||
|
"signal": row.avg_signal,
|
||||||
|
"ccq": row.ccq,
|
||||||
|
"client_count": row.client_count,
|
||||||
|
"frequency": row.frequency,
|
||||||
|
})
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|||||||
Reference in New Issue
Block a user