"use client"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card"; import { DataTable, Column } from "@/components/ui/DataTable"; import { DeviceDetailModal } from "@/components/ui/DeviceDetailModal"; import { useState, useEffect } from "react"; import { TimestampCell } from "@/components/ui/TimestampCell"; import { DeviceStat } from "@/lib/api"; import { useDevices } from "@/lib/api-with-context"; import { fmtBytes } from "@/lib/utils"; import { Loader2, ChevronRight } from "lucide-react"; import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters"; import { HelpTrigger } from "@/components/help/HelpTrigger"; export default function DevicesPage() { const [mounted, setMounted] = useState(false); const devices = useDevices(); const [selectedDevice, setSelectedDevice] = useState(null); const { filters, dateFrom, dateTo, handleFilterChange, setDateFrom, setDateTo, handleReset, activeCount } = useUniversalFilterState(); useEffect(() => { setMounted(true); document.title = "Devices | BackOne - Deep Package Inspection"; }, []); if (!mounted) return null; const all = devices.data || []; const opts = (key: string) => { const raw = Array.from(new Set(all.map((r: any) => String(r[key] || "")).filter(Boolean))); return raw.sort(); }; const filterConfigs: FilterConfig[] = [ { id: "device_type", label: "Device Type", type: "select", value: filters.device_type, options: opts("device_type") }, { id: "os_label", label: "OS", type: "select", value: filters.os_label, options: opts("os_label") }, { id: "manufacturer", label: "Manufacturer", type: "select", value: filters.manufacturer, options: opts("manufacturer") }, { id: "sort_download", label: "Sort Download", type: "select", value: filters.sort_download, options: ["Descending", "Ascending"] }, { id: "sort_upload", label: "Sort Upload", type: "select", value: filters.sort_upload, options: ["Descending", "Ascending"] }, ]; const columns: Column[] = [ { header: "Last Seen", className: "w-[13%]", accessor: (row) => }, { header: "#", accessor: (row, i) => i + 1, className: "w-[4%]" }, { header: "IP Address", className: "w-[12%]", accessor: (row) => (
{row.device_label && ( {row.device_label} )}
), }, { header: "MAC Address", className: "w-[17%]", accessor: (row) => { const isRouted = row.is_gateway_routed === 1; const hasMan = row.manufacturer && row.manufacturer !== "-" && row.manufacturer !== "Unknown"; const info = hasMan ? row.manufacturer : (row.device_type && row.device_type !== "-" && row.device_type !== "Generic Client" ? row.device_type : ""); return row.mac_address ? (
{row.mac_address} {(isRouted || info) && ( {isRouted ? "Via Routed Gateway" : ""} {isRouted && info ? ` • ${info}` : (!isRouted ? info : "")} )}
) : ( "-" ); }, }, { header: "Type", accessor: (row) => row.device_type || "-", className: "w-[11%]" }, { header: "OS", accessor: (row) => row.os_label || "-", className: "w-[11%]" }, { header: "Manufacturer", accessor: (row) => row.manufacturer || "-", className: "w-[14%]" }, { header: "Download", accessor: (row) => {fmtBytes(row.download)}, className: "w-[9%]", }, { header: "Upload", accessor: (row) => {fmtBytes(row.upload)}, className: "w-[9%]", }, ]; const filteredData = all.filter((row: any) => { if (filters.device_type && filters.device_type !== "All" && row.device_type !== filters.device_type) return false; if (filters.os_label && filters.os_label !== "All" && row.os_label !== filters.os_label) return false; if (filters.manufacturer && filters.manufacturer !== "All" && row.manufacturer !== filters.manufacturer) return false; if (dateFrom || dateTo) { const d = row.last_seen ? new Date(row.last_seen).toISOString().slice(0, 10) : ""; if (dateFrom && d < dateFrom) return false; if (dateTo && d > dateTo) return false; } return true; }); let processedData = [...filteredData]; const sortUpload = filters.sort_upload || "All"; const sortDownload = filters.sort_download || "All"; if (sortUpload !== "All") { processedData.sort((a, b) => sortUpload === "Descending" ? b.upload - a.upload : a.upload - b.upload); } else if (sortDownload !== "All") { processedData.sort((a, b) => sortDownload === "Descending" ? b.download - a.download : a.download - b.download); } const minTime = all.reduce((min: number, r: any) => { if (r.last_seen) { const time = new Date(r.last_seen).getTime(); if (isFinite(time) && time < min) return time; } return min; }, Infinity); const minDate = minTime !== Infinity ? new Date(minTime).toISOString().slice(0, 10) : "2024-01-01"; return (

Devices {devices.isLoading && }

Monitor and manage connected network devices.{" "} Click an IP to see app usage & flows.

{ if (id === 'sort_download') { handleFilterChange('sort_download', val); handleFilterChange('sort_upload', 'All'); } else if (id === 'sort_upload') { handleFilterChange('sort_upload', val); handleFilterChange('sort_download', 'All'); } else { handleFilterChange(id, val); } }} showDateRange={true} dateFrom={dateFrom} dateTo={dateTo} minDate={minDate} onDateChange={(f, t) => { setDateFrom(f); setDateTo(t); }} activeFilterCount={activeCount} onReset={handleReset} /> Active Devices ({processedData.length}) { const q = query.trim().toLowerCase(); const matchesSearch = ( (row.ip_address || "").toLowerCase().includes(q) || (row.mac_address || "").toLowerCase().includes(q) || (row.os_label || "").toLowerCase().includes(q) || (row.device_type || "").toLowerCase().includes(q) ); return matchesSearch; }} csvExport={{ filename: `devices-${new Date().toISOString().slice(0,10)}.csv`, headers: ["IP Address", "Device Label", "MAC Address", "Device Type", "OS", "Manufacturer", "Download (bytes)", "Upload (bytes)", "Last Seen"], rowSerializer: (row) => [ row.ip_address || "", row.device_label || "", row.mac_address || "", row.device_type || "", row.os_label || "", row.manufacturer || "", row.download ?? 0, row.upload ?? 0, row.last_seen ? new Date(row.last_seen).toISOString() : "", ], }} /> {/* Device Detail Modal */} {selectedDevice && ( setSelectedDevice(null)} /> )}
); }