@@ -1,16 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { tableData } from "@/data/nodeDataStatic";
|
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { NodeTable } from "@/components/table";
|
import { NodeTable } from "@/components/table";
|
||||||
import { useMachines } from "@/components/hooks/useMachines";
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
//const { data, isLoading } = useMachines();
|
|
||||||
//console.log({ data, isLoading });
|
|
||||||
return (
|
return (
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<NodeTable tableData={tableData} />
|
<NodeTable />
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Toolbar from "@mui/material/Toolbar";
|
|
||||||
import Grid2 from "@mui/material/Unstable_Grid2";
|
import Grid2 from "@mui/material/Unstable_Grid2";
|
||||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||||
import { useTheme } from "@mui/material";
|
import { useTheme } from "@mui/material";
|
||||||
|
|
||||||
import { NodeStatus, TableData } from "@/data/nodeData";
|
|
||||||
import { PieCards } from "./pieCards";
|
import { PieCards } from "./pieCards";
|
||||||
import { PieData, NodePieChart } from "./nodePieChart";
|
import { PieData, NodePieChart } from "./nodePieChart";
|
||||||
|
import { Machine } from "@/api/model/machine";
|
||||||
|
import { Status } from "@/api/model";
|
||||||
|
|
||||||
interface EnhancedTableToolbarProps {
|
interface EnhancedTableToolbarProps {
|
||||||
tableData: TableData[];
|
tableData: readonly Machine[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EnhancedTableToolbar(
|
export function EnhancedTableToolbar(
|
||||||
@@ -24,13 +24,13 @@ export function EnhancedTableToolbar(
|
|||||||
|
|
||||||
const pieData: PieData[] = useMemo(() => {
|
const pieData: PieData[] = useMemo(() => {
|
||||||
const online = tableData.filter(
|
const online = tableData.filter(
|
||||||
(row) => row.status === NodeStatus.Online,
|
(row) => row.status === Status.online,
|
||||||
).length;
|
).length;
|
||||||
const offline = tableData.filter(
|
const offline = tableData.filter(
|
||||||
(row) => row.status === NodeStatus.Offline,
|
(row) => row.status === Status.offline,
|
||||||
).length;
|
).length;
|
||||||
const pending = tableData.filter(
|
const pending = tableData.filter(
|
||||||
(row) => row.status === NodeStatus.Pending,
|
(row) => row.status === Status.unknown,
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -10,15 +10,13 @@ import CircleIcon from "@mui/icons-material/Circle";
|
|||||||
import Stack from "@mui/material/Stack/Stack";
|
import Stack from "@mui/material/Stack/Stack";
|
||||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||||
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
|
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
|
||||||
|
|
||||||
import Grid2 from "@mui/material/Unstable_Grid2"; // Grid version 2
|
import Grid2 from "@mui/material/Unstable_Grid2"; // Grid version 2
|
||||||
import { Collapse, useMediaQuery, useTheme } from "@mui/material";
|
import { Collapse } from "@mui/material";
|
||||||
|
import { Machine, Status } from "@/api/model";
|
||||||
|
|
||||||
import { NodeStatus, NodeStatusKeys, TableData } from "@/data/nodeData";
|
function renderStatus(status: Status) {
|
||||||
|
|
||||||
function renderStatus(status: NodeStatusKeys) {
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case NodeStatus.Online:
|
case Status.online:
|
||||||
return (
|
return (
|
||||||
<Stack direction="row" alignItems="center" gap={1}>
|
<Stack direction="row" alignItems="center" gap={1}>
|
||||||
<CircleIcon color="success" style={{ fontSize: 15 }} />
|
<CircleIcon color="success" style={{ fontSize: 15 }} />
|
||||||
@@ -28,7 +26,7 @@ function renderStatus(status: NodeStatusKeys) {
|
|||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
||||||
case NodeStatus.Offline:
|
case Status.offline:
|
||||||
return (
|
return (
|
||||||
<Stack direction="row" alignItems="center" gap={1}>
|
<Stack direction="row" alignItems="center" gap={1}>
|
||||||
<CircleIcon color="error" style={{ fontSize: 15 }} />
|
<CircleIcon color="error" style={{ fontSize: 15 }} />
|
||||||
@@ -37,7 +35,7 @@ function renderStatus(status: NodeStatusKeys) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
case NodeStatus.Pending:
|
case Status.unknown:
|
||||||
return (
|
return (
|
||||||
<Stack direction="row" alignItems="center" gap={1}>
|
<Stack direction="row" alignItems="center" gap={1}>
|
||||||
<CircleIcon color="warning" style={{ fontSize: 15 }} />
|
<CircleIcon color="warning" style={{ fontSize: 15 }} />
|
||||||
@@ -49,16 +47,12 @@ function renderStatus(status: NodeStatusKeys) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export function NodeRow(props: {
|
export function NodeRow(props: {
|
||||||
row: TableData;
|
row: Machine;
|
||||||
selected: string | undefined;
|
selected: string | undefined;
|
||||||
setSelected: (a: string | undefined) => void;
|
setSelected: (a: string | undefined) => void;
|
||||||
}) {
|
}) {
|
||||||
const theme = useTheme();
|
|
||||||
const is_phone = useMediaQuery(theme.breakpoints.down("md"));
|
|
||||||
|
|
||||||
const { row, selected, setSelected } = props;
|
const { row, selected, setSelected } = props;
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
//const labelId = `enhanced-table-checkbox-${index}`;
|
|
||||||
|
|
||||||
// Speed optimization. We compare string pointers here instead of the string content.
|
// Speed optimization. We compare string pointers here instead of the string content.
|
||||||
const isSelected = selected == row.name;
|
const isSelected = selected == row.name;
|
||||||
@@ -109,15 +103,6 @@ export function NodeRow(props: {
|
|||||||
>
|
>
|
||||||
{renderStatus(row.status)}
|
{renderStatus(row.status)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
|
||||||
align="right"
|
|
||||||
onClick={(event) => handleClick(event, row.name)}
|
|
||||||
>
|
|
||||||
<Typography component="div" align="left" variant="body1">
|
|
||||||
{String(row.last_seen).padStart(3, "0")}{" "}
|
|
||||||
{is_phone ? "days" : "days ago"}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
{/* Row Expansion */}
|
{/* Row Expansion */}
|
||||||
|
|||||||
@@ -1,28 +1,22 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, ChangeEvent, SetStateAction, Dispatch } from "react";
|
import { useState, ChangeEvent, useMemo } from "react";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import TablePagination from "@mui/material/TablePagination";
|
import TablePagination from "@mui/material/TablePagination";
|
||||||
import Paper from "@mui/material/Paper";
|
import Paper from "@mui/material/Paper";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import { CircularProgress, Grid, useTheme } from "@mui/material";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
|
||||||
import SearchIcon from "@mui/icons-material/Search";
|
|
||||||
import { useTheme } from "@mui/material";
|
|
||||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||||
|
|
||||||
import { TableData } from "@/data/nodeData";
|
|
||||||
import { EnhancedTableToolbar } from "./enhancedTableToolbar";
|
import { EnhancedTableToolbar } from "./enhancedTableToolbar";
|
||||||
import { StickySpeedDial } from "./stickySpeedDial";
|
import { StickySpeedDial } from "./stickySpeedDial";
|
||||||
import { NodeTableContainer } from "./nodeTableContainer";
|
import { NodeTableContainer } from "./nodeTableContainer";
|
||||||
import { SearchBar } from "./searchBar";
|
import { SearchBar } from "./searchBar";
|
||||||
import Grid2 from "@mui/material/Unstable_Grid2/Grid2";
|
import Grid2 from "@mui/material/Unstable_Grid2/Grid2";
|
||||||
|
import { useMachines } from "../hooks/useMachines";
|
||||||
|
import { Machine } from "@/api/model/machine";
|
||||||
|
|
||||||
export interface NodeTableProps {
|
export function NodeTable() {
|
||||||
tableData: TableData[];
|
const machines = useMachines();
|
||||||
}
|
|
||||||
|
|
||||||
export function NodeTable(props: NodeTableProps) {
|
|
||||||
let { tableData } = props;
|
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const is_xs = useMediaQuery(theme.breakpoints.only("xs"));
|
const is_xs = useMediaQuery(theme.breakpoints.only("xs"));
|
||||||
@@ -30,7 +24,15 @@ export function NodeTable(props: NodeTableProps) {
|
|||||||
const [selected, setSelected] = useState<string | undefined>(undefined);
|
const [selected, setSelected] = useState<string | undefined>(undefined);
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||||
const [filteredList, setFilteredList] = useState<TableData[]>(tableData);
|
const [filteredList, setFilteredList] = useState<readonly Machine[]>([]);
|
||||||
|
|
||||||
|
const tableData = useMemo(() => {
|
||||||
|
const tableData = machines.data.map((machine) => {
|
||||||
|
return { name: machine.name, status: machine.status };
|
||||||
|
});
|
||||||
|
setFilteredList(tableData);
|
||||||
|
return tableData;
|
||||||
|
}, [machines.data]);
|
||||||
|
|
||||||
const handleChangePage = (event: unknown, newPage: number) => {
|
const handleChangePage = (event: unknown, newPage: number) => {
|
||||||
setPage(newPage);
|
setPage(newPage);
|
||||||
@@ -41,6 +43,18 @@ export function NodeTable(props: NodeTableProps) {
|
|||||||
setPage(0);
|
setPage(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (machines.isLoading) {
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
style={{ height: "100vh" }} // make the container fill the screen height
|
||||||
|
alignItems="center" // center the items vertically
|
||||||
|
justifyContent="center" // center the items horizontally
|
||||||
|
>
|
||||||
|
<CircularProgress size={80} color="secondary" />
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: "100%" }}>
|
<Box sx={{ width: "100%" }}>
|
||||||
<Paper sx={{ width: "100%", mb: 2 }}>
|
<Paper sx={{ width: "100%", mb: 2 }}>
|
||||||
@@ -53,6 +67,7 @@ export function NodeTable(props: NodeTableProps) {
|
|||||||
/>
|
/>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</EnhancedTableToolbar>
|
</EnhancedTableToolbar>
|
||||||
|
|
||||||
<NodeTableContainer
|
<NodeTableContainer
|
||||||
tableData={filteredList}
|
tableData={filteredList}
|
||||||
page={page}
|
page={page}
|
||||||
@@ -61,6 +76,7 @@ export function NodeTable(props: NodeTableProps) {
|
|||||||
selected={selected}
|
selected={selected}
|
||||||
setSelected={setSelected}
|
setSelected={setSelected}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */}
|
{/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */}
|
||||||
<TablePagination
|
<TablePagination
|
||||||
rowsPerPageOptions={[5, 10, 25]}
|
rowsPerPageOptions={[5, 10, 25]}
|
||||||
@@ -76,3 +92,4 @@ export function NodeTable(props: NodeTableProps) {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,13 +12,11 @@ import TableSortLabel from "@mui/material/TableSortLabel";
|
|||||||
import { visuallyHidden } from "@mui/utils";
|
import { visuallyHidden } from "@mui/utils";
|
||||||
import { NodeRow } from "./nodeRow";
|
import { NodeRow } from "./nodeRow";
|
||||||
|
|
||||||
import { TableData } from "@/data/nodeData";
|
import { Machine } from "@/api/model/machine";
|
||||||
|
|
||||||
import { useMediaQuery, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
interface HeadCell {
|
interface HeadCell {
|
||||||
disablePadding: boolean;
|
disablePadding: boolean;
|
||||||
id: keyof TableData;
|
id: keyof Machine;
|
||||||
label: string;
|
label: string;
|
||||||
alignRight: boolean;
|
alignRight: boolean;
|
||||||
}
|
}
|
||||||
@@ -28,7 +26,7 @@ const headCells: readonly HeadCell[] = [
|
|||||||
id: "name",
|
id: "name",
|
||||||
alignRight: false,
|
alignRight: false,
|
||||||
disablePadding: false,
|
disablePadding: false,
|
||||||
label: "DISPLAY NAME & ID",
|
label: "DOMAIN NAME",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "status",
|
id: "status",
|
||||||
@@ -36,12 +34,6 @@ const headCells: readonly HeadCell[] = [
|
|||||||
disablePadding: false,
|
disablePadding: false,
|
||||||
label: "STATUS",
|
label: "STATUS",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "last_seen",
|
|
||||||
alignRight: false,
|
|
||||||
disablePadding: false,
|
|
||||||
label: "LAST SEEN",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
||||||
@@ -90,7 +82,7 @@ function stableSort<T>(
|
|||||||
interface EnhancedTableProps {
|
interface EnhancedTableProps {
|
||||||
onRequestSort: (
|
onRequestSort: (
|
||||||
event: React.MouseEvent<unknown>,
|
event: React.MouseEvent<unknown>,
|
||||||
property: keyof TableData,
|
property: keyof Machine,
|
||||||
) => void;
|
) => void;
|
||||||
order: NodeOrder;
|
order: NodeOrder;
|
||||||
orderBy: string;
|
orderBy: string;
|
||||||
@@ -100,7 +92,7 @@ interface EnhancedTableProps {
|
|||||||
function EnhancedTableHead(props: EnhancedTableProps) {
|
function EnhancedTableHead(props: EnhancedTableProps) {
|
||||||
const { order, orderBy, onRequestSort } = props;
|
const { order, orderBy, onRequestSort } = props;
|
||||||
const createSortHandler =
|
const createSortHandler =
|
||||||
(property: keyof TableData) => (event: React.MouseEvent<unknown>) => {
|
(property: keyof Machine) => (event: React.MouseEvent<unknown>) => {
|
||||||
onRequestSort(event, property);
|
onRequestSort(event, property);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,7 +127,7 @@ function EnhancedTableHead(props: EnhancedTableProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface NodeTableContainerProps {
|
interface NodeTableContainerProps {
|
||||||
tableData: readonly TableData[];
|
tableData: readonly Machine[];
|
||||||
page: number;
|
page: number;
|
||||||
rowsPerPage: number;
|
rowsPerPage: number;
|
||||||
dense: boolean;
|
dense: boolean;
|
||||||
@@ -146,10 +138,7 @@ interface NodeTableContainerProps {
|
|||||||
export function NodeTableContainer(props: NodeTableContainerProps) {
|
export function NodeTableContainer(props: NodeTableContainerProps) {
|
||||||
const { tableData, page, rowsPerPage, dense, selected, setSelected } = props;
|
const { tableData, page, rowsPerPage, dense, selected, setSelected } = props;
|
||||||
const [order, setOrder] = React.useState<NodeOrder>("asc");
|
const [order, setOrder] = React.useState<NodeOrder>("asc");
|
||||||
const [orderBy, setOrderBy] = React.useState<keyof TableData>("status");
|
const [orderBy, setOrderBy] = React.useState<keyof Machine>("status");
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
const is_phone = useMediaQuery(theme.breakpoints.down("sm"));
|
|
||||||
|
|
||||||
// Avoid a layout jump when reaching the last page with empty rows.
|
// Avoid a layout jump when reaching the last page with empty rows.
|
||||||
const emptyRows =
|
const emptyRows =
|
||||||
@@ -157,7 +146,7 @@ export function NodeTableContainer(props: NodeTableContainerProps) {
|
|||||||
|
|
||||||
const handleRequestSort = (
|
const handleRequestSort = (
|
||||||
event: React.MouseEvent<unknown>,
|
event: React.MouseEvent<unknown>,
|
||||||
property: keyof TableData,
|
property: keyof Machine,
|
||||||
) => {
|
) => {
|
||||||
const isAsc = orderBy === property && order === "asc";
|
const isAsc = orderBy === property && order === "asc";
|
||||||
setOrder(isAsc ? "desc" : "asc");
|
setOrder(isAsc ? "desc" : "asc");
|
||||||
@@ -185,7 +174,7 @@ export function NodeTableContainer(props: NodeTableContainerProps) {
|
|||||||
{visibleRows.map((row, index) => {
|
{visibleRows.map((row, index) => {
|
||||||
return (
|
return (
|
||||||
<NodeRow
|
<NodeRow
|
||||||
key={row.name}
|
key={index}
|
||||||
row={row}
|
row={row}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
setSelected={setSelected}
|
setSelected={setSelected}
|
||||||
|
|||||||
@@ -1,34 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import { SetStateAction, Dispatch, useState, useEffect, useMemo } from "react";
|
||||||
SetStateAction,
|
|
||||||
Dispatch,
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useMemo,
|
|
||||||
ClassAttributes,
|
|
||||||
JSX,
|
|
||||||
Key,
|
|
||||||
LiHTMLAttributes,
|
|
||||||
} from "react";
|
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
|
||||||
import SearchIcon from "@mui/icons-material/Search";
|
import SearchIcon from "@mui/icons-material/Search";
|
||||||
import { useDebounce } from "../hooks/useDebounce";
|
import { useDebounce } from "../hooks/useDebounce";
|
||||||
import { TableData } from "@/data/nodeData";
|
import { Autocomplete, InputAdornment, TextField } from "@mui/material";
|
||||||
import {
|
import { Machine } from "@/api/model/machine";
|
||||||
Autocomplete,
|
|
||||||
Box,
|
|
||||||
Container,
|
|
||||||
InputAdornment,
|
|
||||||
Stack,
|
|
||||||
TextField,
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
export interface SearchBarProps {
|
export interface SearchBarProps {
|
||||||
tableData: TableData[];
|
tableData: readonly Machine[];
|
||||||
setFilteredList: Dispatch<SetStateAction<TableData[]>>;
|
setFilteredList: Dispatch<SetStateAction<readonly Machine[]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SearchBar(props: SearchBarProps) {
|
export function SearchBar(props: SearchBarProps) {
|
||||||
@@ -52,7 +33,7 @@ export function SearchBar(props: SearchBarProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debouncedSearch) {
|
if (debouncedSearch) {
|
||||||
const filtered: TableData[] = tableData.filter((row) => {
|
const filtered: Machine[] = tableData.filter((row) => {
|
||||||
return row.name.toLowerCase().includes(debouncedSearch.toLowerCase());
|
return row.name.toLowerCase().includes(debouncedSearch.toLowerCase());
|
||||||
});
|
});
|
||||||
setFilteredList(filtered);
|
setFilteredList(filtered);
|
||||||
|
|||||||
Reference in New Issue
Block a user