feat: Guest WiFi, network intelligence, firmware orchestration, platform automation, api-ssl (v0.16.3-beta)
Guest WiFi (Hotspot): one-click captive-portal wizard that creates the guest SSID (virtual AP per radio), VLAN/bridge segregation + L3 VLAN interface, pool, DHCP, portal, rate-limited guest profile, and masquerade; vouchers with limits + printable sheets; live guest sessions; walled garden. Network intelligence: per-client device fingerprinting (vendor/hostname → category) with a persisted per-client override on the detail page; rogue/neighbor AP detection from stored AP scans (evil-twin flagging); Mist-style anomaly insights (client-count/CPU vs same-hour 14-day baseline + error bursts) in the Operations engine. Firmware orchestration: staged wave-based RouterOS rollouts (canary first) with pre-upgrade backup, reboot-ride-out, version verification, halt-on-failure, and scheduling; live per-device progress + cancel. Platform & automation: scoped API tokens (mtm_, read/write, hashed at rest, Bearer auth in requireAuth); outbound webhooks (12 events, HMAC-signed) fired from the alert pipeline + firmware orchestrator; scheduled email reports. api-ssl: RouterOSClient negotiates TLS on port 8729 and accepts RouterOS's default self-signed certs (rejectUnauthorized:false), so SSL-secured API devices connect without manual cert trust. Fixes #57. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
efb430d934
commit
9dfb8a5333
@@ -1,7 +1,11 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient, keepPreviousData } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Search, Wifi, Network, Users, X, Pencil, Trash2, ChevronUp, ChevronDown, ChevronsUpDown, ChevronLeft, ChevronRight, RefreshCw } from 'lucide-react';
|
||||
import {
|
||||
Search, Wifi, Network, Users, X, Pencil, Trash2, ChevronUp, ChevronDown, ChevronsUpDown,
|
||||
ChevronLeft, ChevronRight, RefreshCw,
|
||||
} from 'lucide-react';
|
||||
import { CATEGORY_META } from '../utils/clientCategories';
|
||||
import { clientsApi } from '../services/api';
|
||||
import type { Client } from '../types';
|
||||
import { useCanWrite } from '../hooks/useCanWrite';
|
||||
@@ -46,6 +50,17 @@ function getPageList(current0: number, totalPages: number): (number | 'ellipsis'
|
||||
return out;
|
||||
}
|
||||
|
||||
function CategoryIcon({ category }: { category?: string }) {
|
||||
const meta = category && category !== 'unknown' ? CATEGORY_META[category] : undefined;
|
||||
if (!meta) return null;
|
||||
const Icon = meta.icon;
|
||||
return (
|
||||
<span title={meta.label} className="flex-shrink-0 text-gray-400 dark:text-slate-500">
|
||||
<Icon className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function formatDataBytes(bytes: number): string {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
@@ -365,7 +380,8 @@ export default function ClientsPage() {
|
||||
className="hover:bg-gray-50 dark:hover:bg-slate-700/30"
|
||||
>
|
||||
<td className="px-4 py-2.5">
|
||||
<div className="flex items-center gap-1 min-w-0">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<CategoryIcon category={client.device_category} />
|
||||
<button
|
||||
onClick={() => navigate(`/clients/${encodeURIComponent(client.mac_address)}`)}
|
||||
className="font-medium text-blue-600 dark:text-blue-400 hover:underline text-left truncate"
|
||||
|
||||
Reference in New Issue
Block a user