github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/dashboard/frontend/src/pages/Dashboard.tsx (about) 1 import React, { FC, useCallback, useEffect, useState, useMemo } from 'react' 2 import bluebird from 'bluebird' 3 import { 4 ComposedChart, 5 Line, 6 Area, 7 Bar, 8 XAxis, 9 YAxis, 10 CartesianGrid, 11 Tooltip, 12 Legend, 13 ResponsiveContainer, 14 PieChart, 15 Pie, 16 Sector, 17 Cell, 18 RadialBarChart, 19 RadialBar, 20 } from 'recharts' 21 22 import Box from '@mui/material/Box' 23 import Grid from '@mui/material/Grid' 24 import Container from '@mui/material/Container' 25 import Paper from '@mui/material/Paper' 26 27 import NumberHighlight from '../components/dashboard/NumberHighlight' 28 import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion' 29 import TimelineIcon from '@mui/icons-material/Timeline' 30 import PersonIcon from '@mui/icons-material/Person' 31 import CodeIcon from '@mui/icons-material/Code' 32 33 import useLoadingErrorHandler from '../hooks/useLoadingErrorHandler' 34 import useApi from '../hooks/useApi' 35 36 import { 37 DashboardSummary, 38 } from '../types' 39 40 const blue = '#0088FE' 41 const green = '#00C49F' 42 const yellow = '#FFBB28' 43 const COLORS = [blue, green, yellow, '#FF8042']; 44 45 const Dashboard: FC = () => { 46 const api = useApi() 47 const loadingErrorHandler = useLoadingErrorHandler() 48 49 const [ data, setData ] = useState<DashboardSummary>() 50 51 const loadData = useCallback(async () => { 52 const handler = loadingErrorHandler(async () => { 53 const data: DashboardSummary = await bluebird.props({ 54 annotations: await api.get('/api/v1/summary/annotations'), 55 jobMonths: await api.get('/api/v1/summary/jobmonths'), 56 jobExecutors: await api.get('/api/v1/summary/jobexecutors'), 57 totalJobs: await api.get('/api/v1/summary/totaljobs'), 58 totalEvents: await api.get('/api/v1/summary/totaljobevents'), 59 totalUsers: await api.get('/api/v1/summary/totalusers'), 60 totalExecutors: await api.get('/api/v1/summary/totalexecutors'), 61 }) 62 setData(data) 63 }) 64 await handler() 65 }, []) 66 67 const barGraphData = useMemo(() => { 68 if(!data?.jobMonths) return [] 69 const mappedData = data?.jobMonths.map((month) => ({ 70 name: month.month, 71 jobs: month.count, 72 })) 73 return [{ 74 name: '', 75 jobs: 0, 76 }].concat(mappedData) 77 }, [ 78 data, 79 ]) 80 81 const pieChartData = useMemo(() => { 82 if(!data?.jobExecutors) return [] 83 return data.jobExecutors.map((executor, index) => ({ 84 name: executor.executor, 85 value: executor.count, 86 })) 87 }, [ 88 data, 89 ]) 90 91 useEffect(() => { 92 loadData() 93 }, []) 94 95 if(!data) return null 96 97 return ( 98 <Container maxWidth={ 'xl' } sx={{ mt: 4, mb: 4 }}> 99 <Grid container spacing={3}> 100 <Grid item xs={12} md={3}> 101 <NumberHighlight 102 headline={ `${data.totalJobs.count}` } 103 subline="Total Jobs" 104 backgroundColor="#D1E9FC" 105 textColor="#061B64" 106 > 107 <AutoAwesomeMotionIcon 108 sx={{ 109 fontSize: '64px', 110 mb: 2, 111 color: '#061B64' 112 }} 113 /> 114 </NumberHighlight> 115 </Grid> 116 <Grid item xs={12} md={3}> 117 <NumberHighlight 118 headline={ `${data.totalEvents.count}` } 119 subline="Total Events" 120 backgroundColor="#D0F2FF" 121 textColor="#264B90" 122 > 123 <TimelineIcon 124 sx={{ 125 fontSize: '64px', 126 mb: 2, 127 color: '#264B90' 128 }} 129 /> 130 </NumberHighlight> 131 </Grid> 132 <Grid item xs={12} md={3}> 133 <NumberHighlight 134 headline={ `${data.totalUsers.count}` } 135 subline="Unique Users" 136 backgroundColor="#FFF7CD" 137 textColor="#7F5509" 138 > 139 <PersonIcon 140 sx={{ 141 fontSize: '64px', 142 mb: 2, 143 color: '#7F5509' 144 }} 145 /> 146 </NumberHighlight> 147 </Grid> 148 149 150 <Grid item xs={12} md={3}> 151 <NumberHighlight 152 headline={ `${data.totalExecutors.count}` } 153 subline="Executors" 154 backgroundColor="#FFE7D9" 155 textColor="#7A0C2E" 156 > 157 <CodeIcon 158 sx={{ 159 fontSize: '64px', 160 mb: 2, 161 color: '#7A0C2E' 162 }} 163 /> 164 </NumberHighlight> 165 </Grid> 166 167 <Grid item xs={12} md={8}> 168 <Box 169 component="div" 170 sx={{ 171 height: '400px', 172 backgroundColor: '#fff', 173 borderRadius: '15px', 174 padding: '20px', 175 }} 176 > 177 <ResponsiveContainer width="100%" height="100%"> 178 <ComposedChart 179 data={barGraphData} 180 margin={{ 181 top: 20, 182 right: 20, 183 bottom: 20, 184 left: 20, 185 }} 186 > 187 <CartesianGrid stroke="#f5f5f5" /> 188 <XAxis dataKey="name" scale="band" /> 189 <YAxis /> 190 <Tooltip /> 191 <Legend /> 192 <Bar dataKey="jobs" barSize={20} fill={ blue } /> 193 <Line type="monotone" dataKey="jobs" stroke={ green } /> 194 </ComposedChart> 195 </ResponsiveContainer> 196 </Box> 197 </Grid> 198 199 <Grid item xs={12} md={4}> 200 <Box 201 component="div" 202 sx={{ 203 height: '400px', 204 backgroundColor: '#fff', 205 borderRadius: '15px', 206 padding: '20px', 207 }} 208 > 209 <ResponsiveContainer width="100%" height="100%"> 210 <PieChart> 211 <Pie 212 data={pieChartData} 213 innerRadius={40} 214 outerRadius={80} 215 fill="#8884d8" 216 paddingAngle={5} 217 dataKey="value" 218 > 219 {pieChartData.map((entry, index) => ( 220 <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> 221 ))} 222 </Pie> 223 <Legend 224 verticalAlign="bottom" 225 layout="vertical" 226 formatter={(value, entry: any, index) => { 227 return `${value} ${entry.payload?.value}` 228 }} 229 /> 230 </PieChart> 231 </ResponsiveContainer> 232 </Box> 233 </Grid> 234 </Grid> 235 </Container> 236 ) 237 } 238 239 export default Dashboard