-
-
-
{children}
+
setShowSidebar(false)}
+ />
+
+
+
+
+ setShowSidebar((c) => !c)}
+ >
+ {!showSidebar && }
+
+
+
+
+
+
+
+
+
diff --git a/pkgs/ui/src/app/nodes/NodeList.tsx b/pkgs/ui/src/app/nodes/NodeList.tsx
index cd82dced0..e52a4cc70 100644
--- a/pkgs/ui/src/app/nodes/NodeList.tsx
+++ b/pkgs/ui/src/app/nodes/NodeList.tsx
@@ -1,38 +1,43 @@
-"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';
-import NodePieChart, { PieData } from './NodePieChart';
-import Grid2 from '@mui/material/Unstable_Grid2'; // Grid version 2
-import { Card, CardContent, Container, FormGroup, useTheme } from '@mui/material';
-import hexRgb from 'hex-rgb';
-import useMediaQuery from '@mui/material/useMediaQuery';
+"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";
+import NodePieChart, { PieData } from "./NodePieChart";
+import Grid2 from "@mui/material/Unstable_Grid2"; // Grid version 2
+import {
+ Card,
+ CardContent,
+ Container,
+ FormGroup,
+ useTheme,
+} from "@mui/material";
+import hexRgb from "hex-rgb";
+import useMediaQuery from "@mui/material/useMediaQuery";
export interface TableData {
name: string;
@@ -47,7 +52,6 @@ export enum NodeStatus {
Pending,
}
-
interface HeadCell {
disablePadding: boolean;
id: keyof TableData;
@@ -57,22 +61,22 @@ interface HeadCell {
const headCells: readonly HeadCell[] = [
{
- id: 'name',
+ id: "name",
alignRight: false,
disablePadding: false,
- label: 'DISPLAY NAME & ID',
+ label: "DISPLAY NAME & ID",
},
{
- id: 'status',
+ id: "status",
alignRight: false,
disablePadding: false,
- label: 'STATUS',
+ label: "STATUS",
},
{
- id: 'last_seen',
+ id: "last_seen",
alignRight: false,
disablePadding: false,
- label: 'LAST SEEN',
+ label: "LAST SEEN",
},
];
@@ -86,7 +90,7 @@ function descendingComparator
(a: T, b: T, orderBy: keyof T) {
return 0;
}
-type Order = 'asc' | 'desc';
+type Order = "asc" | "desc";
function getComparator(
order: Order,
@@ -95,7 +99,7 @@ function getComparator(
a: { [key in Key]: number | string | boolean },
b: { [key in Key]: number | string | boolean },
) => number {
- return order === 'desc'
+ return order === "desc"
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
@@ -104,7 +108,10 @@ function getComparator(
// 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) {
+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]);
@@ -116,18 +123,18 @@ function stableSort(array: readonly T[], comparator: (a: T, b: T) => number)
return stabilizedThis.map((el) => el[0]);
}
-
-
interface EnhancedTableProps {
- onRequestSort: (event: React.MouseEvent, property: keyof TableData) => void;
+ onRequestSort: (
+ event: React.MouseEvent,
+ property: keyof TableData,
+ ) => void;
order: Order;
orderBy: string;
rowCount: number;
}
function EnhancedTableHead(props: EnhancedTableProps) {
- const { order, orderBy, onRequestSort } =
- props;
+ const { order, orderBy, onRequestSort } = props;
const createSortHandler =
(property: keyof TableData) => (event: React.MouseEvent) => {
onRequestSort(event, property);
@@ -139,19 +146,19 @@ function EnhancedTableHead(props: EnhancedTableProps) {
{headCells.map((headCell) => (
{headCell.label}
{orderBy === headCell.id ? (
- {order === 'desc' ? 'sorted descending' : 'sorted ascending'}
+ {order === "desc" ? "sorted descending" : "sorted ascending"}
) : null}
@@ -162,8 +169,6 @@ function EnhancedTableHead(props: EnhancedTableProps) {
);
}
-
-
interface EnhancedTableToolbarProps {
selected: string | undefined;
tableData: TableData[];
@@ -172,41 +177,49 @@ interface EnhancedTableToolbarProps {
function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
const { selected, onClear, tableData } = props;
const theme = useTheme();
- const matches = useMediaQuery(theme.breakpoints.down('lg'));
+ const matches = useMediaQuery(theme.breakpoints.down("lg"));
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 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;
+ 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: '#2E7D32' },
- { name: 'Offline', value: offline, color: '#db3927' },
- { name: 'Pending', value: pending, color: '#FFBB28' },
+ { name: "Online", value: online, color: "#2E7D32" },
+ { name: "Offline", value: offline, color: "#db3927" },
+ { name: "Pending", value: pending, color: "#FFBB28" },
];
}, [tableData]);
const cardData = React.useMemo(() => {
- return pieData.filter((pieItem) => pieItem.value > 0).concat(
- {
- name: 'Total',
+ return pieData
+ .filter((pieItem) => pieItem.value > 0)
+ .concat({
+ name: "Total",
value: pieData.reduce((a, b) => a + b.value, 0),
- color: '#000000'
- }
- );
+ color: "#000000",
+ });
}, [pieData]);
const cardStack = (
@@ -217,17 +230,38 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
display="flex"
flexDirection="column"
justifyContent="flex-start"
- flexWrap="wrap">
+ flexWrap="wrap"
+ >
{cardData.map((pieItem) => (
-
-
-
+
+
+
{pieItem.value}
-
+
{pieItem.name}
-
))}
@@ -240,15 +274,19 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
pl: { sm: 2 },
pr: { xs: 1, sm: 1 },
bgcolor: (theme) =>
- alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity),
- }}>
+ alpha(
+ theme.palette.primary.main,
+ theme.palette.action.activatedOpacity,
+ ),
+ }}
+ >
-
+
);
const unselectedToolbar = (
@@ -270,16 +308,15 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
pr: { xs: 1, sm: 1 },
}}
>
-
+
-
+
);
-
return (
@@ -295,19 +332,41 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
{/* Debug Controls */}
- { setDebug(!debug) }} checked={debug} />} label="Debug" />
+ {
+ setDebug(!debug);
+ }}
+ checked={debug}
+ />
+ }
+ label="Debug"
+ />
{/* Pie Chart Grid */}
-
+
{/* Card Stack Grid */}
-
+
{cardStack}
@@ -315,12 +374,10 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
{isSelected ? selectedToolbar : unselectedToolbar}
-
);
}
-
function renderLastSeen(last_seen: number) {
return (
@@ -376,13 +433,13 @@ function renderStatus(status: NodeStatus) {
}
export interface NodeTableProps {
- tableData: TableData[]
+ tableData: TableData[];
}
export default function NodeTable(props: NodeTableProps) {
let { tableData } = props;
- const [order, setOrder] = React.useState('asc');
- const [orderBy, setOrderBy] = React.useState('status');
+ 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);
@@ -392,8 +449,8 @@ export default function NodeTable(props: NodeTableProps) {
event: React.MouseEvent,
property: keyof TableData,
) => {
- const isAsc = orderBy === property && order === 'asc';
- setOrder(isAsc ? 'desc' : 'asc');
+ const isAsc = orderBy === property && order === "asc";
+ setOrder(isAsc ? "desc" : "asc");
setOrderBy(property);
};
@@ -410,7 +467,9 @@ export default function NodeTable(props: NodeTableProps) {
setPage(newPage);
};
- const handleChangeRowsPerPage = (event: React.ChangeEvent) => {
+ const handleChangeRowsPerPage = (
+ event: React.ChangeEvent,
+ ) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
@@ -431,18 +490,20 @@ export default function NodeTable(props: NodeTableProps) {
[order, orderBy, page, rowsPerPage, tableData],
);
-
-
return (
-
-
- setSelected(undefined)} />
+
+
+ setSelected(undefined)}
+ />
-
+
{renderName(row.name, row.id)}
- {renderStatus(row.status)}
- {renderLastSeen(row.last_seen)}
+
+ {renderStatus(row.status)}
+
+
+ {renderLastSeen(row.last_seen)}
+
);
})}
diff --git a/pkgs/ui/src/app/nodes/NodePieChart.tsx b/pkgs/ui/src/app/nodes/NodePieChart.tsx
index f85a513a6..6e715826e 100644
--- a/pkgs/ui/src/app/nodes/NodePieChart.tsx
+++ b/pkgs/ui/src/app/nodes/NodePieChart.tsx
@@ -1,45 +1,50 @@
-import React, { PureComponent } from 'react';
-import { PieChart, Pie, Sector, Cell, ResponsiveContainer, Legend } from 'recharts';
-import { useTheme } from '@mui/material/styles';
-import { Box, Color } from '@mui/material';
-
+import React, { PureComponent } from "react";
+import {
+ PieChart,
+ Pie,
+ Sector,
+ Cell,
+ ResponsiveContainer,
+ Legend,
+} from "recharts";
+import { useTheme } from "@mui/material/styles";
+import { Box, Color } from "@mui/material";
export interface PieData {
- name: string;
- value: number;
- color: string;
-};
+ name: string;
+ value: number;
+ color: string;
+}
interface Props {
- data: PieData[];
- showLabels?: boolean;
-};
+ data: PieData[];
+ showLabels?: boolean;
+}
-export default function NodePieChart(props: Props ) {
- const theme = useTheme();
- const {data, showLabels} = props;
+export default function NodePieChart(props: Props) {
+ const theme = useTheme();
+ const { data, showLabels } = props;
-
- return (
-
-
-
-
- {data.map((entry, index) => (
- |
- ))}
-
-
-
-
-
- );
-};
+ return (
+
+
+
+
+ {data.map((entry, index) => (
+ |
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/pkgs/ui/src/app/nodes/page.tsx b/pkgs/ui/src/app/nodes/page.tsx
index 3ed9eda18..03993bb5f 100644
--- a/pkgs/ui/src/app/nodes/page.tsx
+++ b/pkgs/ui/src/app/nodes/page.tsx
@@ -1,4 +1,4 @@
-"use client"
+"use client";
import { StrictMode } from "react";
import NodeList, { NodeStatus, TableData } from "./NodeList";
@@ -6,42 +6,98 @@ import NodeList, { NodeStatus, TableData } from "./NodeList";
import Box from "@mui/material/Box";
function createData(
- name: string,
- id: string,
- status: NodeStatus,
- last_seen: number,
-
+ name: string,
+ id: string,
+ status: NodeStatus,
+ last_seen: number,
): TableData {
-
-
- return {
- name,
- id,
- status,
- last_seen: last_seen,
- };
+ return {
+ name,
+ id,
+ status,
+ last_seen: last_seen,
+ };
}
const tableData = [
- createData('Matchbox', "42:0:f21:6916:e333:c47e:4b5c:e74c", NodeStatus.Pending, 0),
- createData('Ahorn', "42:0:3c46:b51c:b34d:b7e1:3b02:8d24", NodeStatus.Online, 0),
- createData('Yellow', "42:0:3c46:98ac:9c80:4f25:50e3:1d8f", NodeStatus.Offline, 16.0),
- createData('Rauter', "42:0:61ea:b777:61ea:803:f885:3523", NodeStatus.Offline, 6.0),
- createData('Porree', "42:0:e644:4499:d034:895e:34c8:6f9a", NodeStatus.Offline, 13),
- createData('Helsinki', "42:0:3c46:fd4a:acf9:e971:6036:8047", NodeStatus.Online, 0),
- createData('Kelle', "42:0:3c46:362d:a9aa:4996:c78e:839a", NodeStatus.Online, 0),
- createData('Shodan', "42:0:3c46:6745:adf4:a844:26c4:bf91", NodeStatus.Online, 0.0),
- createData('Qubasa', "42:0:3c46:123e:bbea:3529:db39:6764", NodeStatus.Offline, 7.0),
- createData('Green', "42:0:a46e:5af:632c:d2fe:a71d:cde0", NodeStatus.Offline, 2),
- createData('Gum', "42:0:e644:238d:3e46:c884:6ec5:16c", NodeStatus.Offline, 0),
- createData('Xu', "42:0:ca48:c2c2:19fb:a0e9:95b9:794f", NodeStatus.Online, 0),
- createData('Zaatar', "42:0:3c46:156e:10b6:3bd6:6e82:b2cd", NodeStatus.Online, 0),
+ createData(
+ "Matchbox",
+ "42:0:f21:6916:e333:c47e:4b5c:e74c",
+ NodeStatus.Pending,
+ 0,
+ ),
+ createData(
+ "Ahorn",
+ "42:0:3c46:b51c:b34d:b7e1:3b02:8d24",
+ NodeStatus.Online,
+ 0,
+ ),
+ createData(
+ "Yellow",
+ "42:0:3c46:98ac:9c80:4f25:50e3:1d8f",
+ NodeStatus.Offline,
+ 16.0,
+ ),
+ createData(
+ "Rauter",
+ "42:0:61ea:b777:61ea:803:f885:3523",
+ NodeStatus.Offline,
+ 6.0,
+ ),
+ createData(
+ "Porree",
+ "42:0:e644:4499:d034:895e:34c8:6f9a",
+ NodeStatus.Offline,
+ 13,
+ ),
+ createData(
+ "Helsinki",
+ "42:0:3c46:fd4a:acf9:e971:6036:8047",
+ NodeStatus.Online,
+ 0,
+ ),
+ createData(
+ "Kelle",
+ "42:0:3c46:362d:a9aa:4996:c78e:839a",
+ NodeStatus.Online,
+ 0,
+ ),
+ createData(
+ "Shodan",
+ "42:0:3c46:6745:adf4:a844:26c4:bf91",
+ NodeStatus.Online,
+ 0.0,
+ ),
+ createData(
+ "Qubasa",
+ "42:0:3c46:123e:bbea:3529:db39:6764",
+ NodeStatus.Offline,
+ 7.0,
+ ),
+ createData(
+ "Green",
+ "42:0:a46e:5af:632c:d2fe:a71d:cde0",
+ NodeStatus.Offline,
+ 2,
+ ),
+ createData("Gum", "42:0:e644:238d:3e46:c884:6ec5:16c", NodeStatus.Offline, 0),
+ createData("Xu", "42:0:ca48:c2c2:19fb:a0e9:95b9:794f", NodeStatus.Online, 0),
+ createData(
+ "Zaatar",
+ "42:0:3c46:156e:10b6:3bd6:6e82:b2cd",
+ NodeStatus.Online,
+ 0,
+ ),
];
export default function Page() {
- return (
-
-
-
- );
-}
\ No newline at end of file
+ return (
+
+
+
+ );
+}
diff --git a/pkgs/ui/src/app/page.tsx b/pkgs/ui/src/app/page.tsx
index 4918af51a..093b3e98a 100644
--- a/pkgs/ui/src/app/page.tsx
+++ b/pkgs/ui/src/app/page.tsx
@@ -1,13 +1,60 @@
-import { Button } from "@mui/material";
+interface DashboardCardProps {
+ children?: React.ReactNode;
+}
+const DashboardCard = (props: DashboardCardProps) => {
+ const { children } = props;
+ return (
+
+ {children}
+
+ );
+};
+
+interface DashboardPanelProps {
+ children?: React.ReactNode;
+}
+const DashboardPanel = (props: DashboardPanelProps) => {
+ const { children } = props;
+ return (
+
+ {children}
+
+ );
+};
+
+interface SplitDashboardCardProps {
+ children?: React.ReactNode[];
+}
+const SplitDashboardCard = (props: SplitDashboardCardProps) => {
+ const { children } = props;
+ return (
+
+
+ {children?.map((row, idx) => (
+
+ {row}
+
+ ))}
+
+
+ );
+};
export default function Dashboard() {
return (
-
-
- Welcome to the Dashboard
-
+
+
+
Current CLAN Overview
+
Recent Activity Log
+
+ Notifications
+ Quick Action
+
+
Panel
+
Side Bar (misc)
);
diff --git a/pkgs/ui/src/app/theme/themes.ts b/pkgs/ui/src/app/theme/themes.ts
index 065381894..fd845dec0 100644
--- a/pkgs/ui/src/app/theme/themes.ts
+++ b/pkgs/ui/src/app/theme/themes.ts
@@ -1,16 +1,13 @@
import { createTheme } from "@mui/material/styles";
-
export const darkTheme = createTheme({
palette: {
mode: "dark",
-
},
});
export const lightTheme = createTheme({
palette: {
mode: "light",
-
},
});
diff --git a/pkgs/ui/src/components/sidebar/index.tsx b/pkgs/ui/src/components/sidebar/index.tsx
index 8418af748..8c8a32d8e 100644
--- a/pkgs/ui/src/components/sidebar/index.tsx
+++ b/pkgs/ui/src/components/sidebar/index.tsx
@@ -1,5 +1,7 @@
import {
Divider,
+ Icon,
+ IconButton,
List,
ListItem,
ListItemButton,
@@ -7,7 +9,7 @@ import {
ListItemText,
} from "@mui/material";
import Image from "next/image";
-import { ReactNode } from "react";
+import { ReactNode, useState } from "react";
import DashboardIcon from "@mui/icons-material/Dashboard";
import DevicesIcon from "@mui/icons-material/Devices";
@@ -16,6 +18,9 @@ import AppsIcon from "@mui/icons-material/Apps";
import DesignServicesIcon from "@mui/icons-material/DesignServices";
import BackupIcon from "@mui/icons-material/Backup";
import Link from "next/link";
+import { tw } from "@/utils/tailwind";
+
+import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
type MenuEntry = {
icon: ReactNode;
@@ -58,11 +63,23 @@ const menuEntries: MenuEntry[] = [
},
];
-export function Sidebar() {
+const hideSidebar = tw`-translate-x-12 absolute lg:-translate-x-64`;
+const showSidebar = tw`lg:translate-x-0 static`;
+
+interface SidebarProps {
+ show: boolean;
+ onClose: () => void;
+}
+export function Sidebar(props: SidebarProps) {
+ const { show, onClose } = props;
return (
-