feat: The Other Dude v9.0.1 — full-featured email system
ci: add GitHub Pages deployment workflow for docs site Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
199
frontend/src/lib/networkApi.ts
Normal file
199
frontend/src/lib/networkApi.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Network Intelligence API client -- types and functions for topology,
|
||||
* VPN tunnels, device logs, client tracking, and interface utilization.
|
||||
*/
|
||||
|
||||
import { api } from './api'
|
||||
import { configEditorApi } from './configEditorApi'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Topology Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface TopologyNode {
|
||||
id: string
|
||||
hostname: string
|
||||
ip: string
|
||||
status: string
|
||||
model: string | null
|
||||
uptime: string | null
|
||||
}
|
||||
|
||||
export interface TopologyEdge {
|
||||
source: string
|
||||
target: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface TopologyResponse {
|
||||
nodes: TopologyNode[]
|
||||
edges: TopologyEdge[]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// VPN Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface VpnTunnel {
|
||||
type: 'wireguard' | 'ipsec' | 'l2tp'
|
||||
remote_endpoint: string
|
||||
status: string
|
||||
uptime: string | null
|
||||
rx_bytes: string | null
|
||||
tx_bytes: string | null
|
||||
local_address: string | null
|
||||
// WireGuard-specific
|
||||
public_key?: string
|
||||
last_handshake?: string
|
||||
// IPsec-specific
|
||||
state?: string
|
||||
}
|
||||
|
||||
export interface VpnResponse {
|
||||
tunnels: VpnTunnel[]
|
||||
device_id: string
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Log Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface LogEntry {
|
||||
time: string
|
||||
topics: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface LogsResponse {
|
||||
logs: LogEntry[]
|
||||
device_id: string
|
||||
count: number
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Client Device Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface ClientDevice {
|
||||
mac: string
|
||||
ip: string
|
||||
interface: string
|
||||
hostname: string | null
|
||||
status: 'reachable' | 'stale'
|
||||
signal_strength: string | null
|
||||
tx_rate: string | null
|
||||
rx_rate: string | null
|
||||
uptime: string | null
|
||||
is_wireless: boolean
|
||||
}
|
||||
|
||||
export interface ClientsResponse {
|
||||
clients: ClientDevice[]
|
||||
device_id: string
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function parseWireGuardPeers(entries: Record<string, string>[]): VpnTunnel[] {
|
||||
return entries.map((entry) => ({
|
||||
type: 'wireguard' as const,
|
||||
remote_endpoint: entry['current-endpoint-address'] || entry['endpoint-address'] || 'unknown',
|
||||
status: entry['current-endpoint-address'] ? 'connected' : 'waiting',
|
||||
uptime: null,
|
||||
rx_bytes: entry.rx || null,
|
||||
tx_bytes: entry.tx || null,
|
||||
local_address: entry['allowed-address'] || null,
|
||||
public_key: entry['public-key'] || undefined,
|
||||
last_handshake: entry['last-handshake'] || undefined,
|
||||
}))
|
||||
}
|
||||
|
||||
function parseIpsecPeers(entries: Record<string, string>[]): VpnTunnel[] {
|
||||
return entries.map((entry) => ({
|
||||
type: 'ipsec' as const,
|
||||
remote_endpoint: entry['remote-address'] || 'unknown',
|
||||
status: entry.state || 'established',
|
||||
uptime: entry.uptime || null,
|
||||
rx_bytes: entry['rx-bytes'] || null,
|
||||
tx_bytes: entry['tx-bytes'] || null,
|
||||
local_address: entry['local-address'] || null,
|
||||
state: entry.state || undefined,
|
||||
}))
|
||||
}
|
||||
|
||||
function parseL2tpServers(entries: Record<string, string>[]): VpnTunnel[] {
|
||||
return entries
|
||||
.filter((entry) => entry.running === 'true' || entry['client-address'])
|
||||
.map((entry) => ({
|
||||
type: 'l2tp' as const,
|
||||
remote_endpoint: entry['client-address'] || entry.name || 'unknown',
|
||||
status: entry.running === 'true' ? 'connected' : 'inactive',
|
||||
uptime: entry.uptime || null,
|
||||
rx_bytes: null,
|
||||
tx_bytes: null,
|
||||
local_address: entry['local-address'] || null,
|
||||
}))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const networkApi = {
|
||||
/**
|
||||
* Fetch network topology (nodes + edges) for a tenant.
|
||||
* Cached on backend with 5-minute Redis TTL.
|
||||
*/
|
||||
getTopology: (tenantId: string) =>
|
||||
api.get<TopologyResponse>(`/api/tenants/${tenantId}/topology`).then((r) => r.data),
|
||||
|
||||
/**
|
||||
* Fetch VPN tunnels by browsing WireGuard peers, IPsec active-peers,
|
||||
* and L2TP server interfaces via the existing config editor browse API.
|
||||
* Uses Promise.allSettled so missing VPN types return empty (not errors).
|
||||
*/
|
||||
getVpnTunnels: async (tenantId: string, deviceId: string): Promise<VpnResponse> => {
|
||||
const [wgResult, ipsecResult, l2tpResult] = await Promise.allSettled([
|
||||
configEditorApi.browse(tenantId, deviceId, '/interface/wireguard/peers'),
|
||||
configEditorApi.browse(tenantId, deviceId, '/ip/ipsec/active-peers'),
|
||||
configEditorApi.browse(tenantId, deviceId, '/interface/l2tp-server/server'),
|
||||
])
|
||||
|
||||
const tunnels: VpnTunnel[] = []
|
||||
|
||||
if (wgResult.status === 'fulfilled' && wgResult.value.success) {
|
||||
tunnels.push(...parseWireGuardPeers(wgResult.value.entries))
|
||||
}
|
||||
if (ipsecResult.status === 'fulfilled' && ipsecResult.value.success) {
|
||||
tunnels.push(...parseIpsecPeers(ipsecResult.value.entries))
|
||||
}
|
||||
if (l2tpResult.status === 'fulfilled' && l2tpResult.value.success) {
|
||||
tunnels.push(...parseL2tpServers(l2tpResult.value.entries))
|
||||
}
|
||||
|
||||
return { tunnels, device_id: deviceId }
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch connected client devices (ARP + DHCP + wireless) from the backend.
|
||||
*/
|
||||
getClients: (tenantId: string, deviceId: string) =>
|
||||
api
|
||||
.get<ClientsResponse>(`/api/tenants/${tenantId}/devices/${deviceId}/clients`)
|
||||
.then((r) => r.data),
|
||||
|
||||
/**
|
||||
* Fetch device syslog entries from the backend logs endpoint.
|
||||
*/
|
||||
getDeviceLogs: (
|
||||
tenantId: string,
|
||||
deviceId: string,
|
||||
params?: { limit?: number; topic?: string; search?: string },
|
||||
) =>
|
||||
api
|
||||
.get<LogsResponse>(`/api/tenants/${tenantId}/devices/${deviceId}/logs`, { params })
|
||||
.then((r) => r.data),
|
||||
}
|
||||
Reference in New Issue
Block a user