adding memory and cpu display to test.amqtt.io dashboard

pull/257/head
Andrew Mirsky 2025-07-08 11:29:42 -04:00
rodzic 57597dfea4
commit fab7c36d86
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: A98E67635CDF2C39
3 zmienionych plików z 176 dodań i 155 usunięć

Wyświetl plik

@ -1,4 +1,17 @@
import React from "react";
export type DataPoint = {
timestamp: string; // ISO format
value: number;
};
// Define the type for each entry in the topic_map
export type TopicEntry<T> = {
current: T;
update: React.Dispatch<React.SetStateAction<T>>;
};
// Define the topic_map type
export type TopicMap = {
[topic: string]: TopicEntry<DataPoint[]>;
};

Wyświetl plik

@ -7,7 +7,7 @@ import SessionsChart from './SessionsChart';
import {useEffect, useState} from "react";
// @ts-ignore
import useMqtt from '../../assets/usemqtt';
import type {DataPoint} from '../../assets/helpers';
import type {DataPoint, TopicMap} from '../../assets/helpers';
import {Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material";
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
import {faGithub, faPython, faDocker, faDiscord} from "@fortawesome/free-brands-svg-icons";
@ -23,6 +23,8 @@ export default function MainGrid() {
const [clientsConnected, setClientsConnected] = useState<DataPoint[]>([]);
const [serverStart, setServerStart] = useState<string>('');
const [serverUptime, setServerUptime] = useState<string>('');
const [cpuPercent, setCpuPercent] = useState<DataPoint[]>([]);
const [memSize, setMemSize] = useState<DataPoint[]>([]);
function getRandomInt(min: number, max: number) {
min = Math.ceil(min);
@ -65,9 +67,21 @@ export default function MainGrid() {
mqttSubscribe('$SYS/broker/uptime/formatted');
mqttSubscribe('$SYS/broker/uptime');
mqttSubscribe('$SYS/broker/clients/connected');
mqttSubscribe('$SYS/broker/cpu/percent');
mqttSubscribe('$SYS/broker/heap/size')
}
}, [isConnected, mqttSubscribe]);
const topic_map: TopicMap = {
'$SYS/broker/messages/publish/sent': { current: sent, update: setSent },
'$SYS/broker/messages/publish/received': { current: received, update: setReceived },
'$SYS/broker/load/bytes/received': { current: bytesIn, update: setBytesIn },
'$SYS/broker/load/bytes/sent': { current: bytesOut, update: setBytesOut },
'$SYS/broker/clients/connected': { current: clientsConnected, update: setClientsConnected },
'$SYS/broker/cpu/percent': { current: cpuPercent, update: setCpuPercent},
'$SYS/broker/heap/size': { current: memSize, update: setMemSize}
};
useEffect(() => {
while (messageQueue.current.length > 0) {
@ -75,37 +89,14 @@ export default function MainGrid() {
try {
const d = payload.message;
if (payload.topic === '$SYS/broker/messages/publish/sent') {
if(payload.topic in topic_map) {
const { update } = topic_map[payload.topic];
const newPoint: DataPoint = {
timestamp: new Date().toISOString(),
value: d
};
setSent(sent => [...sent, newPoint]);
} else if (payload.topic === '$SYS/broker/messages/publish/received') {
const newPoint: DataPoint = {
timestamp: new Date().toISOString(),
value: d
}
setReceived(received => [...received, newPoint]);
} else if (payload.topic === '$SYS/broker/load/bytes/received') {
const newPoint: DataPoint = {
timestamp: new Date().toISOString(),
value: d
}
setBytesIn(bytesIn => [...bytesIn, newPoint]);
} else if (payload.topic === '$SYS/broker/load/bytes/sent') {
const newPoint: DataPoint = {
timestamp: new Date().toISOString(),
value: d
}
setBytesOut(bytesOut => [...bytesOut, newPoint]);
} else if (payload.topic === '$SYS/broker/clients/connected') {
const newPoint: DataPoint = {
timestamp: new Date().toISOString(),
value: d
}
setClientsConnected(clientsConnected => [...clientsConnected, newPoint]);
update(current => [...current, newPoint])
} else if (payload.topic === '$SYS/broker/uptime/formatted') {
const dt = new Date(d + "Z");
setServerStart(dt.toLocaleString());
@ -235,10 +226,10 @@ export default function MainGrid() {
<strong>up for</strong> {serverUptime}
</Grid>
<Grid size={{xs: 12, md: 6}}>
<SessionsChart title={'Sent Messages'} label={'Messages'} data={sent} isConnected={isConnected}/>
<SessionsChart title={'Sent Messages'} label={''} data={sent} isConnected={isConnected}/>
</Grid>
<Grid size={{xs: 12, md: 6}}>
<SessionsChart title={'Received Messages'} label={'Messages'} data={received} isConnected={isConnected}/>
<SessionsChart title={'Received Messages'} label={''} data={received} isConnected={isConnected}/>
</Grid>
<Grid size={{xs: 12, md: 6}}>
<SessionsChart title={'Bytes Out'} label={'Bytes'} data={bytesOut} isConnected={isConnected}/>
@ -249,6 +240,16 @@ export default function MainGrid() {
<Grid size={{xs: 12, md: 6}}>
<SessionsChart title={'Clients Connected'} label={''} data={clientsConnected} isConnected={isConnected}/>
</Grid>
<Grid size={{xs: 12, md: 6}}>
<Grid container spacing={2} columns={2}>
<Grid size={{lg:1}}>
<SessionsChart title={'CPU'} label={'%'} data={cpuPercent} decimals={2} isConnected={isConnected}/>
</Grid>
<Grid size={{lg:1}}>
<SessionsChart title={'Memory'} label={'MB'} data={memSize} decimals={1} isConnected={isConnected}/>
</Grid>
</Grid>
</Grid>
</Grid>
<Grid container spacing={2} columns={12}>

Wyświetl plik

@ -1,147 +1,154 @@
import { useTheme } from '@mui/material/styles';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import { LineChart } from '@mui/x-charts/LineChart';
import CountUp from 'react-countup';
import type { DataPoint } from '../../assets/helpers.jsx';
import {CircularProgress} from "@mui/material";
import { useTheme } from '@mui/material/styles';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import { LineChart } from '@mui/x-charts/LineChart';
import CountUp from 'react-countup';
import type { DataPoint } from '../../assets/helpers.jsx';
import {CircularProgress} from "@mui/material";
const currentTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const currentTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
function formatDate(date: Date) {
function formatDate(date: Date) {
return date.toLocaleTimeString('en-US', {
timeZone: currentTimeZone,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
return date.toLocaleTimeString('en-US', {
timeZone: currentTimeZone,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
}
}
function AreaGradient({ color, id }: { color: string; id: string }) {
return (
<defs>
<linearGradient id={id} x1="50%" y1="0%" x2="50%" y2="100%">
<stop offset="0%" stopColor={color} stopOpacity={0.5} />
<stop offset="100%" stopColor={color} stopOpacity={0} />
</linearGradient>
</defs>
);
}
function AreaGradient({ color, id }: { color: string; id: string }) {
return (
<defs>
<linearGradient id={id} x1="50%" y1="0%" x2="50%" y2="100%">
<stop offset="0%" stopColor={color} stopOpacity={0.5} />
<stop offset="100%" stopColor={color} stopOpacity={0} />
</linearGradient>
</defs>
);
}
function NoDataDisplay(props: any) {
return <>
{!props.isConnected ? <div style={{height: 250, width: 600, paddingTop: 50}}>
<Typography component="h2" variant="subtitle2" gutterBottom>
Connecting...
</Typography>
<CircularProgress size={60}/>
</div> :
<div style={{height: 250, width: 600, paddingTop: 50}}>
<Typography component="h2" variant="subtitle2" gutterBottom>
Connected, waiting for data...
</Typography>
function NoDataDisplay(props: any) {
return <>
{!props.isConnected ? <div style={{height: 250, width: 600, paddingTop: 50}}>
<Typography component="h2" variant="subtitle2" gutterBottom>
Connecting...
</Typography>
<CircularProgress size={60}/>
</div>}
</>
</div> :
<div style={{height: 250, width: 600, paddingTop: 50}}>
<Typography component="h2" variant="subtitle2" gutterBottom>
Connected, waiting for data...
</Typography>
<CircularProgress size={60}/>
</div>}
</>
}
}
function LinearChart(props: any) {
function LinearChart(props: any) {
const theme = useTheme();
const theme = useTheme();
const colorPalette = [
theme.palette.primary.light,
theme.palette.primary.main,
theme.palette.primary.dark,
];
const colorPalette = [
theme.palette.primary.light,
theme.palette.primary.main,
theme.palette.primary.dark,
];
const label: string = props.label || '--';
const label: string = props.label || '--';
return <LineChart
colors={colorPalette}
xAxis={[
{
scaleType: 'point',
data: props.data.map( (dp:DataPoint) =>
formatDate(new Date(dp.timestamp))
),
tickInterval: (_index, i) => (i + 1) % (Math.floor(props.data.length/10) + 1) === 0,
},
]}
series={[
{
id: 'direct',
label: label,
showMark: false,
curve: 'linear',
stack: 'total',
area: true,
stackOrder: 'ascending',
data: props.data.map( (dp:DataPoint) => dp.value),
}
]}
height={175}
margin={{ left: 50, right: 20, top: 20, bottom: 20 }}
grid={{ horizontal: true }}
sx={{
'& .MuiAreaElement-series-organic': {
fill: "url('#organic')",
},
'& .MuiAreaElement-series-referral': {
fill: "url('#referral')",
},
'& .MuiAreaElement-series-direct': {
fill: "url('#direct')",
},
}}
hideLegend
slotProps={{
legend: {
},
}}
>
const baseline: number = props.baseline || 0;
<AreaGradient color={theme.palette.primary.main} id="direct" />
</LineChart>
}
return <LineChart
colors={colorPalette}
xAxis={[
{
scaleType: 'point',
data: props.data.map( (dp:DataPoint) =>
formatDate(new Date(dp.timestamp))
),
tickInterval: (_index, i) => (i + 1) % (Math.floor(props.data.length/10) + 1) === 0,
},
]}
series={[
{
id: 'direct',
label: label,
showMark: false,
curve: 'linear',
stack: 'total',
area: true,
stackOrder: 'ascending',
data: props.data.map( (dp:DataPoint) => dp.value),
baseline: baseline
}
]}
height={175}
export default function SessionsChart(props: any) {
return (
<Card variant="outlined" sx={{ width: '100%' }}>
<CardContent>
<Typography component="h1" variant="subtitle1" gutterBottom>
{props.title}
</Typography>
<Stack sx={{ justifyContent: 'space-between' }}>
<Stack
direction="row"
margin={{ left: 0, right: 20, top: 20, bottom: 20 }}
grid={{ horizontal: true }}
sx={{
alignContent: { xs: 'center', sm: 'flex-start' },
alignItems: 'center',
gap: 1,
'& .MuiAreaElement-series-organic': {
fill: "url('#organic')",
},
'& .MuiAreaElement-series-referral': {
fill: "url('#referral')",
},
'& .MuiAreaElement-series-direct': {
fill: "url('#direct')",
},
}}
hideLegend
slotProps={{
legend: {
},
}}
>
<Typography variant="h4" component="p">
{ props.data.length < 2 ? "" :
<CountUp
start={props.data[props.data.length - 2].value}
end={props.data[props.data.length - 1].value}
duration={5}/>}
</Typography>
<AreaGradient color={theme.palette.primary.main} id="direct" />
</LineChart>
}
export default function SessionsChart(props: any) {
return (
<Card variant="outlined" sx={{ width: '100%' }}>
<CardContent>
<Typography component="h1" variant="subtitle1" gutterBottom>
{props.title}
</Typography>
<Stack sx={{ justifyContent: 'space-between' }}>
<Stack
direction="row"
sx={{
alignContent: { xs: 'center', sm: 'flex-start' },
alignItems: 'center',
gap: 1,
}}
>
<Typography variant="h4" component="p">
{ props.data.length < 2 ? "" :
<CountUp
start={props.data[props.data.length - 2].value}
end={props.data[props.data.length - 1].value}
duration={5}
decimals={props.decimals}
/>} {props.label}
</Typography>
</Stack>
</Stack>
</Stack>
{ props.data.length < 2 ? <NoDataDisplay isConnected={props.isConnected}/> :
<LinearChart {...props} /> }
{ props.data.length < 2 ? <NoDataDisplay isConnected={props.isConnected}/> :
<LinearChart { ...props} baseline={props.data[0].value} /> }
</CardContent>
</Card>
);
}
</CardContent>
</Card>
);
}