diff --git a/pkgs/ui/src/app/nodes/Row.tsx b/pkgs/ui/src/app/nodes/NodeRow.tsx similarity index 73% rename from pkgs/ui/src/app/nodes/Row.tsx rename to pkgs/ui/src/app/nodes/NodeRow.tsx index 421ceaab..7830e9be 100644 --- a/pkgs/ui/src/app/nodes/Row.tsx +++ b/pkgs/ui/src/app/nodes/NodeRow.tsx @@ -1,56 +1,21 @@ "use client"; import * as React from "react"; -import { alpha } from "@mui/material/styles"; import Box from "@mui/material/Box"; -import Table from "@mui/material/Table"; -import TableBody from "@mui/material/TableBody"; import TableCell from "@mui/material/TableCell"; -import TableContainer from "@mui/material/TableContainer"; -import TableHead from "@mui/material/TableHead"; -import TablePagination from "@mui/material/TablePagination"; import TableRow from "@mui/material/TableRow"; -import TableSortLabel from "@mui/material/TableSortLabel"; -import Toolbar from "@mui/material/Toolbar"; import Typography from "@mui/material/Typography"; -import Paper from "@mui/material/Paper"; import IconButton from "@mui/material/IconButton"; -import Tooltip from "@mui/material/Tooltip"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Switch from "@mui/material/Switch"; -import DeleteIcon from "@mui/icons-material/Delete"; -import FilterListIcon from "@mui/icons-material/FilterList"; -import SpeedDial, { CloseReason, OpenReason } from "@mui/material/SpeedDial"; -import SpeedDialIcon from "@mui/material/SpeedDialIcon"; -import SpeedDialAction from "@mui/material/SpeedDialAction"; -import { visuallyHidden } from "@mui/utils"; import CircleIcon from "@mui/icons-material/Circle"; import Stack from "@mui/material/Stack/Stack"; -import EditIcon from "@mui/icons-material/ModeEdit"; -import SearchIcon from "@mui/icons-material/Search"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; -import NodePieChart, { PieData } from "./NodePieChart"; -import Fab from "@mui/material/Fab"; -import AddIcon from "@mui/icons-material/Add"; -import Link from "next/link"; import Grid2 from "@mui/material/Unstable_Grid2"; // Grid version 2 -import { - Card, - CardContent, - Collapse, - Container, - FormGroup, - useTheme, -} from "@mui/material"; -import hexRgb from "hex-rgb"; -import useMediaQuery from "@mui/material/useMediaQuery"; +import { Collapse } from "@mui/material"; import { NodeStatus, NodeStatusKeys, TableData } from "@/data/nodeData"; -import StickySpeedDial from "./StickySpeedDial"; -import { jsx } from "@emotion/react"; -export default function Row(props: { +export default function NodeRow(props: { row: TableData; selected: string | undefined; setSelected: (a: string | undefined) => void; @@ -93,13 +58,13 @@ export default function Row(props: { //const labelId = `enhanced-table-checkbox-${index}`; // Speed optimization. We compare string pointers here instead of the string content. - const isSelected = selected == row.name; + const isSelected = selected == row.id; - const handleClick = (event: React.MouseEvent, name: string) => { + const handleClick = (event: React.MouseEvent, id: string) => { if (isSelected) { setSelected(undefined); } else { - setSelected(name); + setSelected(id); } }; @@ -142,7 +107,7 @@ export default function Row(props: { handleClick(event, row.name)} + onClick={(event) => handleClick(event, row.id)} > diff --git a/pkgs/ui/src/app/nodes/NodeTable.tsx b/pkgs/ui/src/app/nodes/NodeTable.tsx index 121e6c61..085a68c6 100644 --- a/pkgs/ui/src/app/nodes/NodeTable.tsx +++ b/pkgs/ui/src/app/nodes/NodeTable.tsx @@ -1,126 +1,19 @@ "use client"; import * as React from "react"; -import { alpha } from "@mui/material/styles"; import Box from "@mui/material/Box"; -import Table from "@mui/material/Table"; -import TableBody from "@mui/material/TableBody"; -import TableCell from "@mui/material/TableCell"; -import TableContainer from "@mui/material/TableContainer"; -import TableHead from "@mui/material/TableHead"; import TablePagination from "@mui/material/TablePagination"; -import TableRow from "@mui/material/TableRow"; -import TableSortLabel from "@mui/material/TableSortLabel"; -import Toolbar from "@mui/material/Toolbar"; -import Typography from "@mui/material/Typography"; import Paper from "@mui/material/Paper"; import IconButton from "@mui/material/IconButton"; import Tooltip from "@mui/material/Tooltip"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Switch from "@mui/material/Switch"; -import DeleteIcon from "@mui/icons-material/Delete"; -import FilterListIcon from "@mui/icons-material/FilterList"; -import SpeedDial, { CloseReason, OpenReason } from "@mui/material/SpeedDial"; -import SpeedDialIcon from "@mui/material/SpeedDialIcon"; -import SpeedDialAction from "@mui/material/SpeedDialAction"; -import { visuallyHidden } from "@mui/utils"; -import CircleIcon from "@mui/icons-material/Circle"; -import Stack from "@mui/material/Stack/Stack"; -import EditIcon from "@mui/icons-material/ModeEdit"; import SearchIcon from "@mui/icons-material/Search"; -import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; -import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; -import NodePieChart, { PieData } from "./NodePieChart"; -import Fab from "@mui/material/Fab"; -import AddIcon from "@mui/icons-material/Add"; -import Link from "next/link"; -import Row from "./Row"; +import NodeTableContainer from "./NodeTableContainer"; -import Grid2 from "@mui/material/Unstable_Grid2"; // Grid version 2 -import { - Card, - CardContent, - Collapse, - Container, - FormGroup, - useTheme, -} from "@mui/material"; -import hexRgb from "hex-rgb"; +import { useTheme } from "@mui/material"; import useMediaQuery from "@mui/material/useMediaQuery"; -import { NodeStatus, NodeStatusKeys, TableData } from "@/data/nodeData"; +import { TableData } from "@/data/nodeData"; import EnhancedTableToolbar from "./EnhancedTableToolbar"; -import { jsx } from "@emotion/react"; - -interface HeadCell { - disablePadding: boolean; - id: keyof TableData; - label: string; - alignRight: boolean; -} - -const headCells: readonly HeadCell[] = [ - { - id: "name", - alignRight: false, - disablePadding: false, - label: "DISPLAY NAME & ID", - }, - { - id: "status", - alignRight: false, - disablePadding: false, - label: "STATUS", - }, - { - id: "last_seen", - alignRight: false, - disablePadding: false, - label: "LAST SEEN", - }, -]; - -function descendingComparator(a: T, b: T, orderBy: keyof T) { - if (b[orderBy] < a[orderBy]) { - return -1; - } - if (b[orderBy] > a[orderBy]) { - return 1; - } - return 0; -} - -type Order = "asc" | "desc"; - -function getComparator( - order: Order, - orderBy: Key, -): ( - a: { [key in Key]: number | string | boolean }, - b: { [key in Key]: number | string | boolean }, -) => number { - return order === "desc" - ? (a, b) => descendingComparator(a, b, orderBy) - : (a, b) => -descendingComparator(a, b, orderBy); -} - -// Since 2020 all major browsers ensure sort stability with Array.prototype.sort(). -// stableSort() brings sort stability to non-modern browsers (notably IE11). If you -// only support modern browsers you can replace stableSort(exampleArray, exampleComparator) -// with exampleArray.slice().sort(exampleComparator) -function stableSort( - array: readonly T[], - comparator: (a: T, b: T) => number, -) { - const stabilizedThis = array.map((el, index) => [el, index] as [T, number]); - stabilizedThis.sort((a, b) => { - const order = comparator(a[0], b[0]); - if (order !== 0) { - return order; - } - return a[1] - b[1]; - }); - return stabilizedThis.map((el) => el[0]); -} +import { table } from "console"; function SearchBar() { const [search, setSearch] = React.useState(undefined); @@ -145,81 +38,15 @@ export interface NodeTableProps { tableData: TableData[]; } -interface EnhancedTableProps { - onRequestSort: ( - event: React.MouseEvent, - property: keyof TableData, - ) => void; - order: Order; - orderBy: string; - rowCount: number; -} - -function EnhancedTableHead(props: EnhancedTableProps) { - const { order, orderBy, onRequestSort } = props; - const createSortHandler = - (property: keyof TableData) => (event: React.MouseEvent) => { - onRequestSort(event, property); - }; - - return ( - - - - {headCells.map((headCell) => ( - - - {headCell.label} - {orderBy === headCell.id ? ( - - {order === "desc" ? "sorted descending" : "sorted ascending"} - - ) : null} - - - ))} - - - ); -} - export default function NodeTable(props: NodeTableProps) { let { tableData } = props; const theme = useTheme(); const is_xs = useMediaQuery(theme.breakpoints.only("xs")); - const [order, setOrder] = React.useState("asc"); - const [orderBy, setOrderBy] = React.useState("status"); const [selected, setSelected] = React.useState(undefined); const [page, setPage] = React.useState(0); - const [dense, setDense] = React.useState(false); const [rowsPerPage, setRowsPerPage] = React.useState(5); - const [search, setSearch] = React.useState(""); - - const filteredTableData = React.useMemo(() => { - return tableData.filter((row) => { - return null; - }); - }, [search]); - - const handleRequestSort = ( - event: React.MouseEvent, - property: keyof TableData, - ) => { - const isAsc = orderBy === property && order === "asc"; - setOrder(isAsc ? "desc" : "asc"); - setOrderBy(property); - }; const handleChangePage = (event: unknown, newPage: number) => { setPage(newPage); @@ -232,60 +59,18 @@ export default function NodeTable(props: NodeTableProps) { setPage(0); }; - // Avoid a layout jump when reaching the last page with empty rows. - const emptyRows = - page > 0 ? Math.max(0, (1 + page) * rowsPerPage - tableData.length) : 0; - - const visibleRows = React.useMemo( - () => - stableSort(tableData, getComparator(order, orderBy)).slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage, - ), - [order, orderBy, page, rowsPerPage, tableData], - ); - return ( - - - - - {visibleRows.map((row, index) => { - const labelId = `enhanced-table-checkbox-${index}`; - - return ( - - ); - })} - {emptyRows > 0 && ( - - - - )} - -
-
+ {/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */} (a: T, b: T, orderBy: keyof T) { + if (b[orderBy] < a[orderBy]) { + return -1; + } + if (b[orderBy] > a[orderBy]) { + return 1; + } + return 0; +} + +export type NodeOrder = "asc" | "desc"; + +function getComparator( + order: NodeOrder, + orderBy: Key, +): ( + a: { [key in Key]: number | string | boolean }, + b: { [key in Key]: number | string | boolean }, +) => number { + return order === "desc" + ? (a, b) => descendingComparator(a, b, orderBy) + : (a, b) => -descendingComparator(a, b, orderBy); +} + +// Since 2020 all major browsers ensure sort stability with Array.prototype.sort(). +// stableSort() brings sort stability to non-modern browsers (notably IE11). If you +// only support modern browsers you can replace stableSort(exampleArray, exampleComparator) +// with exampleArray.slice().sort(exampleComparator) +function stableSort( + array: readonly T[], + comparator: (a: T, b: T) => number, +) { + const stabilizedThis = array.map((el, index) => [el, index] as [T, number]); + stabilizedThis.sort((a, b) => { + const order = comparator(a[0], b[0]); + if (order !== 0) { + return order; + } + return a[1] - b[1]; + }); + return stabilizedThis.map((el) => el[0]); +} + +interface EnhancedTableProps { + onRequestSort: ( + event: React.MouseEvent, + property: keyof TableData, + ) => void; + order: NodeOrder; + orderBy: string; + rowCount: number; +} + +function EnhancedTableHead(props: EnhancedTableProps) { + const { order, orderBy, onRequestSort } = props; + const createSortHandler = + (property: keyof TableData) => (event: React.MouseEvent) => { + onRequestSort(event, property); + }; + + return ( + + + + {headCells.map((headCell) => ( + + + {headCell.label} + {orderBy === headCell.id ? ( + + {order === "desc" ? "sorted descending" : "sorted ascending"} + + ) : null} + + + ))} + + + ); +} + +interface NodeTableContainerProps { + tableData: readonly TableData[]; + page: number; + rowsPerPage: number; + dense: boolean; + selected: string | undefined; + setSelected: React.Dispatch>; +} + +export default function NodeTableContainer(props: NodeTableContainerProps) { + const { tableData, page, rowsPerPage, dense, selected, setSelected } = props; + const [order, setOrder] = React.useState("asc"); + const [orderBy, setOrderBy] = React.useState("status"); + + // Avoid a layout jump when reaching the last page with empty rows. + const emptyRows = + page > 0 ? Math.max(0, (1 + page) * rowsPerPage - tableData.length) : 0; + + const handleRequestSort = ( + event: React.MouseEvent, + property: keyof TableData, + ) => { + const isAsc = orderBy === property && order === "asc"; + setOrder(isAsc ? "desc" : "asc"); + setOrderBy(property); + }; + + const visibleRows = React.useMemo( + () => + stableSort(tableData, getComparator(order, orderBy)).slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage, + ), + [order, orderBy, page, rowsPerPage, tableData], + ); + return ( + + + + + {visibleRows.map((row, index) => { + const labelId = `enhanced-table-checkbox-${index}`; + + return ( + + ); + })} + {emptyRows > 0 && ( + + + + )} + +
+
+ ); +} diff --git a/pkgs/ui/src/app/nodes/page.tsx b/pkgs/ui/src/app/nodes/page.tsx index e434d6b3..1b0f7985 100644 --- a/pkgs/ui/src/app/nodes/page.tsx +++ b/pkgs/ui/src/app/nodes/page.tsx @@ -3,13 +3,13 @@ import NodeTable from "./NodeTable"; import Box from "@mui/material/Box"; -import { tableData } from "@/data/nodeData"; +import { tableData, executeCreateData } from "@/data/nodeData"; import { StrictMode } from "react"; export default function Page() { return ( - + ); } diff --git a/pkgs/ui/src/data/nodeData.tsx b/pkgs/ui/src/data/nodeData.tsx index 60c982bd..5e536e3a 100644 --- a/pkgs/ui/src/data/nodeData.tsx +++ b/pkgs/ui/src/data/nodeData.tsx @@ -19,6 +19,10 @@ function createData( status: NodeStatusKeys, last_seen: number, ): TableData { + if (status == NodeStatus.Online) { + last_seen = 0; + } + return { name, id, @@ -27,6 +31,67 @@ function createData( }; } +// A function to generate random names +function getRandomName(): string { + let names = [ + "Alice", + "Bob", + "Charlie", + "David", + "Eve", + "Frank", + "Grace", + "Heidi", + "Ivan", + "Judy", + "Mallory", + "Oscar", + "Peggy", + "Sybil", + "Trent", + "Victor", + "Walter", + "Wendy", + "Zoe", + ]; + let index = Math.floor(Math.random() * names.length); + return names[index]; +} + +// A function to generate random IPv6 addresses +function getRandomId(): string { + let hex = "0123456789abcdef"; + let id = ""; + for (let i = 0; i < 8; i++) { + for (let j = 0; j < 4; j++) { + let index = Math.floor(Math.random() * hex.length); + id += hex[index]; + } + if (i < 7) { + id += ":"; + } + } + return id; +} + +// A function to generate random status keys +function getRandomStatus(): NodeStatusKeys { + let statusKeys = [NodeStatus.Online, NodeStatus.Offline, NodeStatus.Pending]; + let index = Math.floor(Math.random() * statusKeys.length); + return statusKeys[index]; +} + +// A function to generate random last seen values +function getRandomLastSeen(status: NodeStatusKeys): number { + if (status === "online") { + return 0; + } else { + let min = 1; // One day ago + let max = 360; // One year ago + return Math.floor(Math.random() * (max - min + 1) + min); + } +} + export const tableData = [ createData( "Matchbox", @@ -97,3 +162,19 @@ export const tableData = [ 0, ), ]; + +// A function to execute the createData function with dummy data in a loop 100 times and return an array +export function executeCreateData(): TableData[] { + let result: TableData[] = []; + for (let i = 0; i < 100; i++) { + // Generate dummy data + let name = getRandomName(); + let id = getRandomId(); + let status = getRandomStatus(); + let last_seen = getRandomLastSeen(status); + + // Call the createData function and push the result to the array + result.push(createData(name, id, status, last_seen)); + } + return result; +}