diff --git a/pkgs/ui/src/app/nodes/EnhancedTableToolbar.tsx b/pkgs/ui/src/app/nodes/EnhancedTableToolbar.tsx new file mode 100644 index 00000000..4f921e65 --- /dev/null +++ b/pkgs/ui/src/app/nodes/EnhancedTableToolbar.tsx @@ -0,0 +1,194 @@ +"use client"; + +import * as React from "react"; +import Box from "@mui/material/Box"; +import Toolbar from "@mui/material/Toolbar"; +import Typography from "@mui/material/Typography"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; +import Stack from "@mui/material/Stack/Stack"; +import NodePieChart from "./NodePieChart"; + +import Grid2 from "@mui/material/Unstable_Grid2"; // Grid version 2 +import { Card, CardContent, FormGroup, useTheme } from "@mui/material"; +import hexRgb from "hex-rgb"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { NodeStatus, TableData } from "@/data/nodeData"; + +interface EnhancedTableToolbarProps { + tableData: TableData[]; +} + +function PieCardData(props: { pieData: PieData[]; debugSx: any }) { + const { pieData, debugSx } = props; + + const cardData = React.useMemo(() => { + return pieData + .filter((pieItem) => pieItem.value > 0) + .concat({ + name: "Total", + value: pieData.reduce((a, b) => a + b.value, 0), + color: "#000000", + }); + }, [pieData]); + + return ( + + {cardData.map((pieItem) => ( + + + + {pieItem.value} + + + {pieItem.name} + + + + ))} + + ); +} + +interface PieData { + name: string; + value: number; + color: string; +} + +export default function EnhancedTableToolbar( + props: React.PropsWithChildren, +) { + const { tableData } = props; + const theme = useTheme(); + const is_lg = useMediaQuery(theme.breakpoints.down("lg")); + const [debug, setDebug] = React.useState(false); + + const debugSx = debug + ? { + "--Grid-borderWidth": "1px", + borderTop: "var(--Grid-borderWidth) solid", + borderLeft: "var(--Grid-borderWidth) solid", + borderColor: "divider", + "& > div": { + borderRight: "var(--Grid-borderWidth) solid", + borderBottom: "var(--Grid-borderWidth) solid", + borderColor: "divider", + }, + } + : {}; + + const pieData: PieData[] = React.useMemo(() => { + const online = tableData.filter( + (row) => row.status === NodeStatus.Online, + ).length; + const offline = tableData.filter( + (row) => row.status === NodeStatus.Offline, + ).length; + const pending = tableData.filter( + (row) => row.status === NodeStatus.Pending, + ).length; + + return [ + { name: "Online", value: online, color: theme.palette.success.main }, + { name: "Offline", value: offline, color: theme.palette.error.main }, + { name: "Pending", value: pending, color: theme.palette.warning.main }, + ]; + }, [tableData, theme]); + + return ( + + + + NODES + + + {/* Debug Controls */} + + + { + setDebug(!debug); + }} + checked={debug} + /> + } + label="Debug" + /> + + + + {/* Pie Chart Grid */} + + + + + + + {/* Card Stack Grid */} + + + + + {/*Toolbar Grid */} + + + {props.children} + + + + ); +} diff --git a/pkgs/ui/src/app/nodes/NodeList.tsx b/pkgs/ui/src/app/nodes/NodeList.tsx deleted file mode 100644 index 9f7a324a..00000000 --- a/pkgs/ui/src/app/nodes/NodeList.tsx +++ /dev/null @@ -1,679 +0,0 @@ -"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 { NodeStatus, NodeStatusKeys, TableData } from "@/data/nodeData"; - -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]); -} - -function CrudSpeedDial(props: { selected: string | undefined }) { - const { selected } = props; - const [open, setOpen] = React.useState(false); - - function handleClose(event: any, reason: CloseReason) { - if (reason === "toggle" || reason === "escapeKeyDown") { - setOpen(false); - } - } - - function handleOpen(event: any, reason: OpenReason) { - if (reason === "toggle") { - setOpen(true); - } - } - - const isSomethingSelected = selected != undefined; - - function editDial() { - if (isSomethingSelected) { - return ( - - - - ); - } else { - return ; - } - } - - return ( - - } - direction="down" - onClose={handleClose} - onOpen={handleOpen} - open={open} - > - - - - } - tooltipTitle="Add" - /> - - - } - tooltipTitle="Delete" - /> - - - - ); -} - -interface EnhancedTableToolbarProps { - selected: string | undefined; - tableData: TableData[]; - onClear: () => void; -} -function EnhancedTableToolbar(props: EnhancedTableToolbarProps) { - const { selected, onClear, tableData } = props; - const theme = useTheme(); - const is_lg = useMediaQuery(theme.breakpoints.down("lg")); - const is_sm = useMediaQuery(theme.breakpoints.down("sm")); - const isSelected = selected != undefined; - const [debug, setDebug] = React.useState(false); - const debugSx = debug - ? { - "--Grid-borderWidth": "1px", - borderTop: "var(--Grid-borderWidth) solid", - borderLeft: "var(--Grid-borderWidth) solid", - borderColor: "divider", - "& > div": { - borderRight: "var(--Grid-borderWidth) solid", - borderBottom: "var(--Grid-borderWidth) solid", - borderColor: "divider", - }, - } - : {}; - - const pieData = React.useMemo(() => { - const online = tableData.filter( - (row) => row.status === NodeStatus.Online, - ).length; - const offline = tableData.filter( - (row) => row.status === NodeStatus.Offline, - ).length; - const pending = tableData.filter( - (row) => row.status === NodeStatus.Pending, - ).length; - - return [ - { name: "Online", value: online, color: theme.palette.success.main }, - { name: "Offline", value: offline, color: theme.palette.error.main }, - { name: "Pending", value: pending, color: theme.palette.warning.main }, - ]; - }, [tableData, theme]); - - const cardData = React.useMemo(() => { - return pieData - .filter((pieItem) => pieItem.value > 0) - .concat({ - name: "Total", - value: pieData.reduce((a, b) => a + b.value, 0), - color: "#000000", - }); - }, [pieData]); - - const cardStack = ( - - {cardData.map((pieItem) => ( - - - - {pieItem.value} - - - {pieItem.name} - - - - ))} - - ); - - return ( - - - - - NODES - - - {/* Debug Controls */} - - - { - setDebug(!debug); - }} - checked={debug} - /> - } - label="Debug" - /> - - - - {/* Pie Chart Grid */} - - - - - - - {/* Card Stack Grid */} - - {cardStack} - - - {/*Toolbar Grid */} - - - - - - - - - - - ); -} - -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} - - - ))} - - - ); -} - -function Row(props: { - row: TableData; - selected: string | undefined; - setSelected: (a: string | undefined) => void; -}) { - function renderStatus(status: NodeStatusKeys) { - switch (status) { - case NodeStatus.Online: - return ( - - - - Online - - - ); - - case NodeStatus.Offline: - return ( - - - - Offline - - - ); - case NodeStatus.Pending: - return ( - - - - Pending - - - ); - } - } - - const { row, selected, setSelected } = props; - const [open, setOpen] = React.useState(false); - //const labelId = `enhanced-table-checkbox-${index}`; - - // Speed optimization. We compare string pointers here instead of the string content. - const isSelected = selected == row.name; - - const handleClick = (event: React.MouseEvent, name: string) => { - if (isSelected) { - setSelected(undefined); - } else { - setSelected(name); - } - }; - - const debug = true; - const debugSx = debug - ? { - "--Grid-borderWidth": "1px", - borderTop: "var(--Grid-borderWidth) solid", - borderLeft: "var(--Grid-borderWidth) solid", - borderColor: "divider", - "& > div": { - borderRight: "var(--Grid-borderWidth) solid", - borderBottom: "var(--Grid-borderWidth) solid", - borderColor: "divider", - }, - } - : {}; - - return ( - - {/* Rendered Row */} - - - setOpen(!open)} - > - {open ? : } - - - handleClick(event, row.name)} - > - - - {row.name} - - - {row.id} - - - - handleClick(event, row.name)} - > - {renderStatus(row.status)} - - handleClick(event, row.name)} - > - - {row.last_seen} days ago - - - - - {/* Row Expansion */} - - - - - - Metadata - - - - Hello1 - - - Hello2 - - - - - - - - ); -} - -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 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); - }; - - const handleChangeRowsPerPage = ( - event: React.ChangeEvent, - ) => { - setRowsPerPage(parseInt(event.target.value, 10)); - 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 ( - - - setSelected(undefined)} - /> - - - - - {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:" */} - -
-
- ); -} diff --git a/pkgs/ui/src/app/nodes/NodeRow.tsx b/pkgs/ui/src/app/nodes/NodeRow.tsx new file mode 100644 index 00000000..7830e9be --- /dev/null +++ b/pkgs/ui/src/app/nodes/NodeRow.tsx @@ -0,0 +1,170 @@ +"use client"; + +import * as React from "react"; +import Box from "@mui/material/Box"; +import TableCell from "@mui/material/TableCell"; +import TableRow from "@mui/material/TableRow"; +import Typography from "@mui/material/Typography"; +import IconButton from "@mui/material/IconButton"; +import CircleIcon from "@mui/icons-material/Circle"; +import Stack from "@mui/material/Stack/Stack"; +import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; +import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; + +import Grid2 from "@mui/material/Unstable_Grid2"; // Grid version 2 +import { Collapse } from "@mui/material"; +import { NodeStatus, NodeStatusKeys, TableData } from "@/data/nodeData"; + +export default function NodeRow(props: { + row: TableData; + selected: string | undefined; + setSelected: (a: string | undefined) => void; +}) { + function renderStatus(status: NodeStatusKeys) { + switch (status) { + case NodeStatus.Online: + return ( + + + + Online + + + ); + + case NodeStatus.Offline: + return ( + + + + Offline + + + ); + case NodeStatus.Pending: + return ( + + + + Pending + + + ); + } + } + + const { row, selected, setSelected } = props; + const [open, setOpen] = React.useState(false); + //const labelId = `enhanced-table-checkbox-${index}`; + + // Speed optimization. We compare string pointers here instead of the string content. + const isSelected = selected == row.id; + + const handleClick = (event: React.MouseEvent, id: string) => { + if (isSelected) { + setSelected(undefined); + } else { + setSelected(id); + } + }; + + const debug = true; + const debugSx = debug + ? { + "--Grid-borderWidth": "1px", + borderTop: "var(--Grid-borderWidth) solid", + borderLeft: "var(--Grid-borderWidth) solid", + borderColor: "divider", + "& > div": { + borderRight: "var(--Grid-borderWidth) solid", + borderBottom: "var(--Grid-borderWidth) solid", + borderColor: "divider", + }, + } + : {}; + + return ( + + {/* Rendered Row */} + + + setOpen(!open)} + > + {open ? : } + + + handleClick(event, row.id)} + > + + + {row.name} + + + {row.id} + + + + handleClick(event, row.name)} + > + {renderStatus(row.status)} + + handleClick(event, row.name)} + > + + {row.last_seen} days ago + + + + + {/* Row Expansion */} + + + + + + Metadata + + + + Hello1 + + + Hello2 + + + + + + + + ); +} diff --git a/pkgs/ui/src/app/nodes/NodeTable.tsx b/pkgs/ui/src/app/nodes/NodeTable.tsx new file mode 100644 index 00000000..26e97b8d --- /dev/null +++ b/pkgs/ui/src/app/nodes/NodeTable.tsx @@ -0,0 +1,97 @@ +"use client"; + +import * as React from "react"; +import Box from "@mui/material/Box"; +import TablePagination from "@mui/material/TablePagination"; +import Paper from "@mui/material/Paper"; +import IconButton from "@mui/material/IconButton"; +import Tooltip from "@mui/material/Tooltip"; +import SearchIcon from "@mui/icons-material/Search"; +import NodeTableContainer from "./NodeTableContainer"; + +import { useTheme } from "@mui/material"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { TableData } from "@/data/nodeData"; +import EnhancedTableToolbar from "./EnhancedTableToolbar"; +import { table } from "console"; +import StickySpeedDial from "./StickySpeedDial"; + +export interface SearchBarProps { + search: string; + setSearch: React.Dispatch>; +} + +function SearchBar(props: SearchBarProps) { + const { search, setSearch } = props; + const handleSearch = (event: React.ChangeEvent) => { + setSearch(event.target.value); + }; + + return ( + + ); +} + +export interface NodeTableProps { + tableData: TableData[]; +} + +export default function NodeTable(props: NodeTableProps) { + let { tableData } = props; + + const theme = useTheme(); + const is_xs = useMediaQuery(theme.breakpoints.only("xs")); + + const [selected, setSelected] = React.useState(undefined); + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + const [search, setSearch] = React.useState(""); + + const handleChangePage = (event: unknown, newPage: number) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = ( + event: React.ChangeEvent, + ) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }; + + return ( + + + + + + + + {/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */} + + + + ); +} diff --git a/pkgs/ui/src/app/nodes/NodeTableContainer.tsx b/pkgs/ui/src/app/nodes/NodeTableContainer.tsx new file mode 100644 index 00000000..67af5718 --- /dev/null +++ b/pkgs/ui/src/app/nodes/NodeTableContainer.tsx @@ -0,0 +1,207 @@ +"use client"; + +import * as React from "react"; +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 TableRow from "@mui/material/TableRow"; +import TableSortLabel from "@mui/material/TableSortLabel"; +import { visuallyHidden } from "@mui/utils"; +import NodeRow from "./NodeRow"; + +import { TableData } from "@/data/nodeData"; + +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; +} + +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) => { + return ( + + ); + })} + {emptyRows > 0 && ( + + + + )} + +
+
+ ); +} diff --git a/pkgs/ui/src/app/nodes/StickySpeedDial.tsx b/pkgs/ui/src/app/nodes/StickySpeedDial.tsx new file mode 100644 index 00000000..d32a1fcf --- /dev/null +++ b/pkgs/ui/src/app/nodes/StickySpeedDial.tsx @@ -0,0 +1,126 @@ +"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 { NodeStatus, NodeStatusKeys, TableData } from "@/data/nodeData"; +import { jsx } from "@emotion/react"; + +export default function StickySpeedDial(props: { + selected: string | undefined; +}) { + const { selected } = props; + const [open, setOpen] = React.useState(false); + + function handleClose(event: any, reason: CloseReason) { + if (reason === "toggle" || reason === "escapeKeyDown") { + setOpen(false); + } + } + + function handleOpen(event: any, reason: OpenReason) { + if (reason === "toggle") { + setOpen(true); + } + } + + const isSomethingSelected = selected != undefined; + + function editDial() { + if (isSomethingSelected) { + return ( + + + + ); + } else { + return ; + } + } + + return ( + + } + direction="down" + onClose={handleClose} + onOpen={handleOpen} + open={open} + > + + + + } + tooltipTitle="Add" + /> + + + } + tooltipTitle="Delete" + /> + + + + ); +} diff --git a/pkgs/ui/src/app/nodes/page.tsx b/pkgs/ui/src/app/nodes/page.tsx index d912c5fc..6a250f29 100644 --- a/pkgs/ui/src/app/nodes/page.tsx +++ b/pkgs/ui/src/app/nodes/page.tsx @@ -1,15 +1,15 @@ "use client"; -import NodeList from "./NodeList"; +import NodeTable from "./NodeTable"; import Box from "@mui/material/Box"; -import { tableData } from "@/data/nodeData"; +import { tableData } from "@/data/nodeDataStatic"; 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; +} diff --git a/pkgs/ui/src/data/nodeDataStatic.tsx b/pkgs/ui/src/data/nodeDataStatic.tsx new file mode 100644 index 00000000..cd97ea65 --- /dev/null +++ b/pkgs/ui/src/data/nodeDataStatic.tsx @@ -0,0 +1,602 @@ +export const tableData = [ + { + name: "Bob", + id: "f435:9678:eff4:cedf:7725:421c:82c0:611c", + status: "Pending", + last_seen: 83, + }, + { + name: "Peggy", + id: "aa49:f231:04ac:f0d2:cf9a:4635:57e3:4b78", + status: "Offline", + last_seen: 294, + }, + { + name: "Wendy", + id: "a453:4ec6:c401:30dc:89a0:1567:90c9:4a72", + status: "Pending", + last_seen: 332, + }, + { + name: "Sybil", + id: "3f14:92c3:1090:bef0:7dc6:9eaf:755c:6f7f", + status: "Offline", + last_seen: 48, + }, + { + name: "Ivan", + id: "855e:d1ff:d20c:d1b8:86c4:9ffe:ca2a:fefa", + status: "Offline", + last_seen: 292, + }, + { + name: "Ivan", + id: "7bfb:3980:dc1a:37e2:2464:6028:393b:315f", + status: "Pending", + last_seen: 240, + }, + { + name: "Mallory", + id: "d457:0a59:ea77:963f:f20b:ca55:57f2:a54b", + status: "Online", + last_seen: 0, + }, + { + name: "Mallory", + id: "bbef:a90a:f7ff:5f40:23fc:a1d8:6b2f:7b0a", + status: "Offline", + last_seen: 95, + }, + { + name: "Zoe", + id: "1057:7b4d:0708:66a1:5d36:5d8c:6620:4f5f", + status: "Online", + last_seen: 0, + }, + { + name: "Ivan", + id: "74d3:60dc:5e12:dc52:7c7f:6903:4ff1:ba20", + status: "Pending", + last_seen: 193, + }, + { + name: "Ivan", + id: "6770:c213:bdd1:0226:26e0:a8d4:949a:f2cc", + status: "Pending", + last_seen: 181, + }, + { + name: "Bob", + id: "5551:7363:ba70:6927:57f3:24ea:386d:e2d9", + status: "Online", + last_seen: 0, + }, + { + name: "Victor", + id: "12eb:9b2b:0c94:0731:15e6:7cd1:8984:7669", + status: "Offline", + last_seen: 130, + }, + { + name: "Victor", + id: "785e:b8a0:289b:5108:a982:ecff:8f0b:9db2", + status: "Offline", + last_seen: 336, + }, + { + name: "Judy", + id: "ee4b:ce86:4885:2729:089e:7f93:d3dd:7a09", + status: "Pending", + last_seen: 241, + }, + { + name: "Frank", + id: "6c05:c65d:ef3b:be64:ae58:e1fd:7aaa:bd30", + status: "Online", + last_seen: 0, + }, + { + name: "Alice", + id: "0fe6:f3b1:58c2:ef75:12de:6514:d1a6:eda5", + status: "Pending", + last_seen: 94, + }, + { + name: "Charlie", + id: "095b:1b3f:cfeb:72f8:e84c:91f7:2c62:06e9", + status: "Pending", + last_seen: 131, + }, + { + name: "Walter", + id: "8d0a:56b0:1537:1b2e:9581:d640:cd29:21cf", + status: "Offline", + last_seen: 273, + }, + { + name: "Walter", + id: "d8ab:813a:cbc6:d379:d3be:cc4b:20b8:94ae", + status: "Offline", + last_seen: 200, + }, + { + name: "Trent", + id: "0199:cb5c:a8cd:af82:2d9a:e63a:a157:6ee6", + status: "Offline", + last_seen: 352, + }, + { + name: "Peggy", + id: "1b7b:8da6:623a:75ea:2385:8777:76b9:9ba8", + status: "Online", + last_seen: 0, + }, + { + name: "Zoe", + id: "6ed3:ee3b:f7df:044a:355a:7790:0bf3:a308", + status: "Online", + last_seen: 0, + }, + { + name: "Wendy", + id: "efed:821d:a944:0775:ead4:e520:6402:8dc4", + status: "Online", + last_seen: 0, + }, + { + name: "Heidi", + id: "b616:82c5:4a37:c020:9568:aaa4:6390:22d5", + status: "Online", + last_seen: 0, + }, + { + name: "Charlie", + id: "2c05:e6fc:a359:bf63:65e7:ee0e:49e3:6cc5", + status: "Online", + last_seen: 0, + }, + { + name: "Frank", + id: "e9cb:eddc:1a94:22e4:4e3d:e927:c599:db04", + status: "Online", + last_seen: 0, + }, + { + name: "Bob", + id: "be03:3537:719f:5152:f277:03ef:c6c9:c953", + status: "Offline", + last_seen: 69, + }, + { + name: "Oscar", + id: "cede:fa4a:518d:b885:d1fa:00f4:d97a:2258", + status: "Pending", + last_seen: 280, + }, + { + name: "Judy", + id: "9337:53e7:1aae:1b94:6035:e601:e562:350c", + status: "Online", + last_seen: 0, + }, + { + name: "Judy", + id: "d50b:0ddc:0c9d:cd34:40ee:34c9:75ff:d16c", + status: "Online", + last_seen: 0, + }, + { + name: "Charlie", + id: "368f:8855:59ed:b8c3:4fff:7630:9948:877f", + status: "Offline", + last_seen: 63, + }, + { + name: "Judy", + id: "0fcd:74ae:8dad:8d10:370b:46c5:a403:eab8", + status: "Pending", + last_seen: 242, + }, + { + name: "Wendy", + id: "285f:3015:2f09:2fde:25ee:87be:6d2c:f4f3", + status: "Pending", + last_seen: 60, + }, + { + name: "Ivan", + id: "942d:9c5c:1f4a:95ba:1046:c643:a874:83ce", + status: "Online", + last_seen: 0, + }, + { + name: "Frank", + id: "06f5:5b67:98e9:31b4:ddba:fb81:afb1:7677", + status: "Offline", + last_seen: 154, + }, + { + name: "Zoe", + id: "ec58:13b0:b7ef:6e19:6c78:2c35:4fa3:092a", + status: "Offline", + last_seen: 264, + }, + { + name: "Charlie", + id: "7151:20e3:3969:2933:c23b:6d9a:9723:d1bd", + status: "Online", + last_seen: 0, + }, + { + name: "David", + id: "0466:bd3f:0fff:2119:b9c0:ee60:2a90:6b54", + status: "Online", + last_seen: 0, + }, + { + name: "David", + id: "814d:b00c:4a98:7aa2:5354:7076:f48e:1609", + status: "Offline", + last_seen: 249, + }, + { + name: "David", + id: "8633:f51b:b643:829a:08b0:fc2a:7cad:abda", + status: "Offline", + last_seen: 359, + }, + { + name: "Eve", + id: "0fd5:7b72:4445:0e27:95c6:1b80:99dc:6590", + status: "Online", + last_seen: 0, + }, + { + name: "Wendy", + id: "dc9b:bac9:8ec8:9167:5da5:8530:fcb7:1458", + status: "Online", + last_seen: 0, + }, + { + name: "Bob", + id: "0412:6da1:82ef:6cb1:467e:34e9:34df:5742", + status: "Online", + last_seen: 0, + }, + { + name: "Ivan", + id: "d03b:aba5:ee40:1961:f824:0d9b:9669:2e3e", + status: "Online", + last_seen: 0, + }, + { + name: "Zoe", + id: "ad06:abfd:05c2:fc17:a8e0:857e:b04c:d3fe", + status: "Offline", + last_seen: 25, + }, + { + name: "Ivan", + id: "d2ca:12cb:d271:44a2:2813:e826:964b:b292", + status: "Pending", + last_seen: 11, + }, + { + name: "Charlie", + id: "aaa9:9754:34e2:7251:af6e:df7a:5422:96cd", + status: "Offline", + last_seen: 131, + }, + { + name: "Mallory", + id: "8511:8f44:45d7:cc8a:43ff:908c:a35f:19bc", + status: "Offline", + last_seen: 141, + }, + { + name: "Alice", + id: "8bc2:8f83:3081:d90c:087e:13bf:002b:934b", + status: "Online", + last_seen: 0, + }, + { + name: "Peggy", + id: "9b33:5e44:310f:f6ab:81ae:df8e:be7b:ddc4", + status: "Pending", + last_seen: 354, + }, + { + name: "Victor", + id: "9776:1f62:b8dc:1d14:bf9c:bf41:99b7:5c11", + status: "Online", + last_seen: 0, + }, + { + name: "David", + id: "874a:a4df:321a:5367:4b63:4a0c:48d6:0a0d", + status: "Offline", + last_seen: 199, + }, + { + name: "Charlie", + id: "05f0:7188:a2fb:cfe3:7d85:e3de:d6c2:07e9", + status: "Pending", + last_seen: 46, + }, + { + name: "Grace", + id: "dcbd:5ddf:496d:dd27:0e98:9905:7c24:664d", + status: "Pending", + last_seen: 284, + }, + { + name: "Ivan", + id: "3cb9:d178:2cb1:ae2c:dd3d:ce93:34e0:cc68", + status: "Online", + last_seen: 0, + }, + { + name: "Frank", + id: "c974:9ef6:df64:188c:4622:aa71:497b:1fd8", + status: "Pending", + last_seen: 18, + }, + { + name: "Frank", + id: "3ba0:9362:3b2f:ec10:03c2:7bd7:67ad:759c", + status: "Online", + last_seen: 0, + }, + { + name: "Walter", + id: "b57d:3d13:6654:d944:0e7c:61d6:9dc0:15d8", + status: "Online", + last_seen: 0, + }, + { + name: "Sybil", + id: "de00:f53f:0be4:8a57:047a:f99d:8ab2:fe7c", + status: "Pending", + last_seen: 41, + }, + { + name: "Trent", + id: "bb32:acd8:bbfb:ea3e:1b58:dc17:4cc4:64ce", + status: "Offline", + last_seen: 326, + }, + { + name: "Oscar", + id: "a3e9:1060:b0df:2adb:a340:3f34:5348:d882", + status: "Offline", + last_seen: 57, + }, + { + name: "Wendy", + id: "6b49:c86c:41c8:c021:2103:b707:26a9:6d6a", + status: "Online", + last_seen: 0, + }, + { + name: "Sybil", + id: "aaf2:68cb:da7e:0844:8a12:5fb9:cff5:9de8", + status: "Online", + last_seen: 0, + }, + { + name: "Victor", + id: "b87d:0cc5:e3be:a600:045e:4a26:f7e2:6b3d", + status: "Offline", + last_seen: 342, + }, + { + name: "Frank", + id: "13e6:1b80:ba7e:6275:21c8:dc96:6ab1:ac69", + status: "Pending", + last_seen: 213, + }, + { + name: "Sybil", + id: "14ad:2010:7648:fbc8:337c:6984:d7e2:5202", + status: "Online", + last_seen: 0, + }, + { + name: "Charlie", + id: "f79c:e3b8:1082:7a9d:7adf:f5b6:e333:affd", + status: "Online", + last_seen: 0, + }, + { + name: "Sybil", + id: "534c:0d80:97d5:7b54:a1c3:5457:c5e8:62ee", + status: "Pending", + last_seen: 133, + }, + { + name: "Victor", + id: "e829:f930:b7d7:6005:cc34:7bca:0163:7903", + status: "Online", + last_seen: 0, + }, + { + name: "Ivan", + id: "18c3:6ab2:93ea:0756:52e4:33a6:0c2b:bd79", + status: "Pending", + last_seen: 109, + }, + { + name: "Eve", + id: "49a3:28ad:6121:2e35:1b72:e5d7:5efc:0626", + status: "Online", + last_seen: 0, + }, + { + name: "Trent", + id: "d900:540b:b764:2468:ad9e:2716:ab4f:9955", + status: "Pending", + last_seen: 117, + }, + { + name: "Zoe", + id: "9d3d:de15:014a:ac0d:74b9:2ae8:08d4:8848", + status: "Pending", + last_seen: 114, + }, + { + name: "Peggy", + id: "2145:0e6a:f66a:8e93:71b7:385d:c25b:6a2b", + status: "Offline", + last_seen: 71, + }, + { + name: "Sybil", + id: "834c:adcc:a3d7:b5fc:d5cd:4209:e280:e625", + status: "Pending", + last_seen: 200, + }, + { + name: "Heidi", + id: "5ecc:904c:2d31:177b:346d:4c23:5169:a982", + status: "Online", + last_seen: 0, + }, + { + name: "Ivan", + id: "0446:9092:762c:48b6:310a:e6fb:be48:4631", + status: "Online", + last_seen: 0, + }, + { + name: "David", + id: "160c:97aa:f4ff:52d0:717e:2263:7262:90ed", + status: "Offline", + last_seen: 275, + }, + { + name: "Charlie", + id: "6832:d934:2a76:27c0:88ea:2b05:5bf8:4f86", + status: "Online", + last_seen: 0, + }, + { + name: "Eve", + id: "5363:e485:d9b9:89a8:3ee4:5d24:b2b5:2ab5", + status: "Offline", + last_seen: 343, + }, + { + name: "Peggy", + id: "5673:e221:3236:0a33:29a9:5c5f:ff03:c98d", + status: "Offline", + last_seen: 130, + }, + { + name: "Grace", + id: "8946:27f1:2abe:f009:b109:f0af:92d1:4c5b", + status: "Pending", + last_seen: 72, + }, + { + name: "Walter", + id: "7bc3:a683:660d:ffc3:a40f:b8ab:2246:ed38", + status: "Offline", + last_seen: 264, + }, + { + name: "Mallory", + id: "5b5d:129e:8c3e:4f34:94bf:ffe5:7a8a:b3ae", + status: "Online", + last_seen: 0, + }, + { + name: "Eve", + id: "d7db:981a:8885:838b:70b6:a691:1c26:4d59", + status: "Offline", + last_seen: 172, + }, + { + name: "Peggy", + id: "1255:426b:034c:0171:d67e:ef4c:d9a5:7da6", + status: "Pending", + last_seen: 81, + }, + { + name: "Frank", + id: "1c79:ef2f:c01a:d53b:016d:e2fc:3566:6b85", + status: "Online", + last_seen: 0, + }, + { + name: "Judy", + id: "b159:2924:969d:e1f6:e295:f19d:9bf2:8f58", + status: "Offline", + last_seen: 221, + }, + { + name: "Judy", + id: "bba4:85aa:bcc7:5ef6:920c:7d74:e921:7d47", + status: "Offline", + last_seen: 168, + }, + { + name: "Victor", + id: "dc3f:5b3a:3e71:552a:9cdb:10f2:699a:d8e9", + status: "Offline", + last_seen: 217, + }, + { + name: "Victor", + id: "e084:f4cf:be5b:83c1:f1f5:1159:d24d:dea2", + status: "Online", + last_seen: 0, + }, + { + name: "Trent", + id: "113c:335a:c844:7307:b192:1c44:fb34:4cc0", + status: "Offline", + last_seen: 77, + }, + { + name: "Sybil", + id: "05a0:cbe8:4b89:47e3:81e9:39b7:4966:bbed", + status: "Offline", + last_seen: 145, + }, + { + name: "Alice", + id: "0f33:461d:c779:067e:424e:b933:c855:c376", + status: "Online", + last_seen: 0, + }, + { + name: "Trent", + id: "a883:16c8:abb4:3ab3:7d6f:beb4:1398:bc9c", + status: "Pending", + last_seen: 189, + }, + { + name: "Wendy", + id: "2e7c:1162:1488:6a1e:34fe:0725:bd56:4461", + status: "Pending", + last_seen: 338, + }, + { + name: "Judy", + id: "c4e1:6d39:5079:097b:3228:1391:4f59:1be6", + status: "Offline", + last_seen: 352, + }, + { + name: "Victor", + id: "d32c:1045:4297:251a:6ec8:16c8:541d:7925", + status: "Offline", + last_seen: 61, + }, + { + name: "Zoe", + id: "b6cf:6f86:f510:9f39:93a9:aba9:4632:bb69", + status: "Online", + last_seen: 0, + }, +];