From b9c6ffd95ce7b7f56fdc287264e7ae6d7e995d31 Mon Sep 17 00:00:00 2001 From: Luis-Hebendanz Date: Wed, 9 Aug 2023 17:31:28 +0200 Subject: [PATCH] UI: Added /nodes page --- pkgs/ui/src/app/nodes/NodeList.tsx | 436 ++++++++++++++++++++++++ pkgs/ui/src/app/nodes/PieData.tsx | 36 ++ pkgs/ui/src/app/nodes/page.tsx | 17 + pkgs/ui/src/app/nodes/styles.module.css | 0 4 files changed, 489 insertions(+) create mode 100644 pkgs/ui/src/app/nodes/NodeList.tsx create mode 100644 pkgs/ui/src/app/nodes/PieData.tsx create mode 100644 pkgs/ui/src/app/nodes/page.tsx create mode 100644 pkgs/ui/src/app/nodes/styles.module.css diff --git a/pkgs/ui/src/app/nodes/NodeList.tsx b/pkgs/ui/src/app/nodes/NodeList.tsx new file mode 100644 index 00000000..21128ec9 --- /dev/null +++ b/pkgs/ui/src/app/nodes/NodeList.tsx @@ -0,0 +1,436 @@ +"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 Checkbox from '@mui/material/Checkbox'; +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 { visuallyHidden } from '@mui/utils'; +import CircleIcon from '@mui/icons-material/Circle'; +import Stack from '@mui/material/Stack/Stack'; +import ModeIcon from '@mui/icons-material/Mode'; +import ClearIcon from '@mui/icons-material/Clear'; +import Fade from '@mui/material/Fade/Fade'; + +interface Data { + name: string; + id: string; + status: boolean; + last_seen: number; +} + +function createData( + name: string, + id: string, + status: boolean, + last_seen: number, + +): Data { + if (status && last_seen > 0) { + console.error("Last seen should be 0 if status is true"); + } + + return { + name, + id, + status, + last_seen: last_seen, + }; +} + +const rows = [ + createData('Matchbox', "42:0:f21:6916:e333:c47e:4b5c:e74c", true, 0), + createData('Ahorn', "42:0:3c46:b51c:b34d:b7e1:3b02:8d24", true, 0), + createData('Yellow', "42:0:3c46:98ac:9c80:4f25:50e3:1d8f", false, 16.0), + createData('Rauter', "42:0:61ea:b777:61ea:803:f885:3523", false, 6.0), + createData('Porree', "42:0:e644:4499:d034:895e:34c8:6f9a", false, 13), + createData('Helsinki', "42:0:3c46:fd4a:acf9:e971:6036:8047", true, 0), + createData('Kelle', "42:0:3c46:362d:a9aa:4996:c78e:839a", true, 0), + createData('Shodan', "42:0:3c46:6745:adf4:a844:26c4:bf91", true, 0.0), + createData('Qubasa', "42:0:3c46:123e:bbea:3529:db39:6764", false, 7.0), + createData('Green', "42:0:a46e:5af:632c:d2fe:a71d:cde0", false, 2), + createData('Gum', "42:0:e644:238d:3e46:c884:6ec5:16c", false, 0), + createData('Xu', "42:0:ca48:c2c2:19fb:a0e9:95b9:794f", true, 0), + createData('Zaatar', "42:0:3c46:156e:10b6:3bd6:6e82:b2cd", true, 0), +]; + +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]); +} + +interface HeadCell { + disablePadding: boolean; + id: keyof Data; + 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', + }, +]; + +interface EnhancedTableProps { + onRequestSort: (event: React.MouseEvent, property: keyof Data) => void; + order: Order; + orderBy: string; + rowCount: number; +} + +function EnhancedTableHead(props: EnhancedTableProps) { + const { order, orderBy, onRequestSort } = + props; + const createSortHandler = + (property: keyof Data) => (event: React.MouseEvent) => { + onRequestSort(event, property); + }; + + return ( + + + {headCells.map((headCell) => ( + + + {headCell.label} + {orderBy === headCell.id ? ( + + {order === 'desc' ? 'sorted descending' : 'sorted ascending'} + + ) : null} + + + ))} + + + ); +} + +interface EnhancedTableToolbarProps { + selected: string | undefined; + onClear: () => void; +} + +function renderLastSeen(last_seen: number) { + return ( + + {last_seen} days ago + + ); +} + +function renderName(name: string, id: string) { + return ( + + + {name} + + + {id} + + + ); +} + +function renderStatus(status: boolean) { + if (status) { + return ( + + + + Online + + + ); + } + return ( + + + + Offline + + + ); +} + +function EnhancedTableToolbar(props: EnhancedTableToolbarProps) { + const { selected, onClear } = props; + const somethingSelected = selected !== undefined; + + const handleSomethingSelected = () => { + + if (somethingSelected) { + return ( + + + alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity), + }}> + + + + + + + {selected} selected + + + + + + + + + + ); + } else { + return ( + + + + Nodes + + + + + + + + + ); + } + }; + + return handleSomethingSelected(); +} + +export default function EnhancedTable() { + 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 Data, + ) => { + const isAsc = orderBy === property && order === 'asc'; + setOrder(isAsc ? 'desc' : 'asc'); + setOrderBy(property); + }; + + const handleClick = (event: React.MouseEvent, name: string) => { + if (selected === name) { + setSelected(undefined); + } else { + setSelected(name); + } + }; + + const handleChangePage = (event: unknown, newPage: number) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event: React.ChangeEvent) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }; + + const handleChangeDense = (event: React.ChangeEvent) => { + setDense(event.target.checked); + }; + + // TODO: Make a number to increase comparison speed and ui performance + const isSelected = (name: string) => name === selected; + + // Avoid a layout jump when reaching the last page with empty rows. + const emptyRows = + page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0; + + const visibleRows = React.useMemo( + () => + stableSort(rows, getComparator(order, orderBy)).slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage, + ), + [order, orderBy, page, rowsPerPage], + ); + + return ( + + + setSelected(undefined)} /> + + + + + {visibleRows.map((row, index) => { + const isItemSelected = isSelected(row.name); + const labelId = `enhanced-table-checkbox-${index}`; + + return ( + handleClick(event, row.name)} + role="checkbox" + aria-checked={isItemSelected} + tabIndex={-1} + key={row.name} + selected={isItemSelected} + sx={{ cursor: 'pointer' }} + > + {/* + + */} + + {renderName(row.name, row.id)} + + {renderStatus(row.status)} + {renderLastSeen(row.last_seen)} + + ); + })} + {emptyRows > 0 && ( + + + + )} + +
+
+ {/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */} + +
+ } + label="Dense padding" + /> +
+ ); +} diff --git a/pkgs/ui/src/app/nodes/PieData.tsx b/pkgs/ui/src/app/nodes/PieData.tsx new file mode 100644 index 00000000..e402fb16 --- /dev/null +++ b/pkgs/ui/src/app/nodes/PieData.tsx @@ -0,0 +1,36 @@ + + +export default function PieData() { + return [ + { + "id": "scala", + "label": "scala", + "value": 317, + "color": "hsl(3, 70%, 50%)" + }, + { + "id": "rust", + "label": "rust", + "value": 489, + "color": "hsl(113, 70%, 50%)" + }, + { + "id": "css", + "label": "css", + "value": 456, + "color": "hsl(17, 70%, 50%)" + }, + { + "id": "elixir", + "label": "elixir", + "value": 343, + "color": "hsl(232, 70%, 50%)" + }, + { + "id": "haskell", + "label": "haskell", + "value": 167, + "color": "hsl(292, 70%, 50%)" + } + ]; +} \ No newline at end of file diff --git a/pkgs/ui/src/app/nodes/page.tsx b/pkgs/ui/src/app/nodes/page.tsx new file mode 100644 index 00000000..545ccee2 --- /dev/null +++ b/pkgs/ui/src/app/nodes/page.tsx @@ -0,0 +1,17 @@ +"use client" + +import { StrictMode } from "react"; +import NodeList from "./NodeList"; + +import Box from "@mui/material/Box"; + + +export default function Page() { + return ( + + + + + + ); +} \ No newline at end of file diff --git a/pkgs/ui/src/app/nodes/styles.module.css b/pkgs/ui/src/app/nodes/styles.module.css new file mode 100644 index 00000000..e69de29b