[release] v0.12.0-unstable24

This commit is contained in:
Yann Stepienik 2023-10-31 19:05:36 +00:00
parent d247708d73
commit 10fe2d7f25
20 changed files with 741 additions and 169 deletions

View file

@ -1,7 +1,7 @@
import wrap from './wrap'; import wrap from './wrap';
function get() { function get(metarr) {
return wrap(fetch('/cosmos/api/metrics', { return wrap(fetch('/cosmos/api/metrics?metrics=' + metarr.join(','), {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'

View file

@ -23,9 +23,9 @@ const dashboard = {
}, },
{ {
id: 'dashboard', id: 'dashboard',
title: 'Dashboard', title: 'Monitoring',
type: 'item', type: 'item',
url: '/cosmos-ui/dashboard', url: '/cosmos-ui/monitoring',
icon: DashboardOutlined, icon: DashboardOutlined,
breadcrumbs: false breadcrumbs: false
}, },

View file

@ -122,6 +122,8 @@ const ConfigManagement = () => {
Expanded: config && config.HomepageConfig && config.HomepageConfig.Expanded, Expanded: config && config.HomepageConfig && config.HomepageConfig.Expanded,
PrimaryColor: config && config.ThemeConfig && config.ThemeConfig.PrimaryColor, PrimaryColor: config && config.ThemeConfig && config.ThemeConfig.PrimaryColor,
SecondaryColor: config && config.ThemeConfig && config.ThemeConfig.SecondaryColor, SecondaryColor: config && config.ThemeConfig && config.ThemeConfig.SecondaryColor,
MonitoringEnabled: !config.MonitoringDisabled,
}} }}
validationSchema={Yup.object().shape({ validationSchema={Yup.object().shape({
@ -141,6 +143,7 @@ const ConfigManagement = () => {
// AutoUpdate: values.AutoUpdate, // AutoUpdate: values.AutoUpdate,
BlockedCountries: values.GeoBlocking, BlockedCountries: values.GeoBlocking,
CountryBlacklistIsWhitelist: values.CountryBlacklistIsWhitelist, CountryBlacklistIsWhitelist: values.CountryBlacklistIsWhitelist,
MonitoringDisabled: !values.MonitoringEnabled,
HTTPConfig: { HTTPConfig: {
...config.HTTPConfig, ...config.HTTPConfig,
Hostname: values.Hostname, Hostname: values.Hostname,
@ -298,6 +301,12 @@ const ConfigManagement = () => {
</TextField> </TextField>
</Stack> </Stack>
</Grid> </Grid>
<CosmosCheckbox
label="Monitoring Enabled"
name="MonitoringEnabled"
formik={formik}
/>
</Grid> </Grid>
</MainCard> </MainCard>
@ -363,29 +372,6 @@ const ConfigManagement = () => {
formik.setFieldValue('PrimaryColor', colorRGB); formik.setFieldValue('PrimaryColor', colorRGB);
SetPrimaryColor(colorRGB); SetPrimaryColor(colorRGB);
}} }}
colors={[
'#ab47bc',
'#4527a0',
'#FF6900',
'#FCB900',
'#7BDCB5',
'#00D084',
'#8ED1FC',
'#0693E3',
'#ABB8C3',
'#EB144C',
'#F78DA7',
'#9900EF',
'#FF0000',
'#FFC0CB',
'#20B2AA',
'#FFFF00',
'#8A2BE2',
'#A52A2A',
'#5F9EA0',
'#7FFF00',
'#D2691E'
]}
/> />
</Stack> </Stack>
</Grid> </Grid>
@ -401,29 +387,6 @@ const ConfigManagement = () => {
formik.setFieldValue('SecondaryColor', colorRGB); formik.setFieldValue('SecondaryColor', colorRGB);
SetSecondaryColor(colorRGB); SetSecondaryColor(colorRGB);
}} }}
colors={[
'#ab47bc',
'#4527a0',
'#FF6900',
'#FCB900',
'#7BDCB5',
'#00D084',
'#8ED1FC',
'#0693E3',
'#ABB8C3',
'#EB144C',
'#F78DA7',
'#9900EF',
'#FF0000',
'#FFC0CB',
'#20B2AA',
'#FFFF00',
'#8A2BE2',
'#A52A2A',
'#5F9EA0',
'#7FFF00',
'#D2691E'
]}
/> />
</Stack> </Stack>
</Grid> </Grid>

View file

@ -0,0 +1,187 @@
import React, { useEffect, useState, useMemo } from 'react';
// material-ui
import {
Avatar,
AvatarGroup,
Box,
Button,
Grid,
List,
ListItemAvatar,
ListItemButton,
ListItemSecondaryAction,
ListItemText,
MenuItem,
Stack,
TextField,
Typography,
Alert
} from '@mui/material';
import MainCard from '../../../components/MainCard';
// material-ui
import { useTheme } from '@mui/material/styles';
// third-party
import ReactApexChart from 'react-apexcharts';
import { FormaterForMetric, formatDate, toUTC } from './utils';
import * as API from '../../../api';
const _MiniPlotComponent = ({metrics, labels}) => {
const theme = useTheme();
const { primary, secondary } = theme.palette.text;
const [dataMetrics, setDataMetrics] = useState([]);
const [series, setSeries] = useState([]);
const slot = 'hourly';
useEffect(() => {
let xAxis = [];
if(slot === 'latest') {
for(let i = 0; i < 100; i++) {
xAxis.unshift(i);
}
}
else if(slot === 'hourly') {
for(let i = 0; i < 48; i++) {
let now = new Date();
now.setHours(now.getHours() - i);
now.setMinutes(0);
now.setSeconds(0);
xAxis.unshift(formatDate(now, true));
}
} else if(slot === 'daily') {
for(let i = 0; i < 30; i++) {
let now = new Date();
now.setDate(now.getDate() - i);
xAxis.unshift(formatDate(now));
}
}
let xAxisLength = xAxis.length;
xAxis = xAxis.filter((x, i) => i >= xAxisLength / 4);
// request data
API.metrics.get(metrics).then((res) => {
const toProcess = res.data || [];
setDataMetrics(toProcess);
const _series = toProcess.map((serie) => ({
name: 'Value',
data: xAxis.map((date) => {
if(slot === 'latest') {
return serie.Values[serie.Values.length - 1 - date] ?
serie.Values[serie.Values.length - 1 - date].Value :
0;
} else {
let key = slot === 'hourly' ? "hour_" : "day_";
let k = key + toUTC(date, slot === 'hourly');
if (k in serie.ValuesAggl) {
return serie.ValuesAggl[k].Value;
} else {
return 0;
}
}
})
}));
setSeries(_series);
});
}, [metrics]);
const chartOptions = {
colors: [
theme.palette.primary.main.replace('rgb(', 'rgba('),
theme.palette.secondary.main.replace('rgb(', 'rgba(')
],
chart: {
type: 'area',
toolbar: {
show: false,
},
zoom: {
enabled: false
},
},
grid: {
show: false,
},
dataLabels: {
enabled: false
},
stroke: {
show: true,
width: 2,
curve: 'smooth'
},
xaxis: {
labels: {
show: false
},
axisBorder: {
show: false
},
axisTicks: {
show: false
}
},
yaxis: dataMetrics.map((data) => ({
labels: {
show: false
},
axisBorder: {
show: false
},
axisTicks: {
show: false
}
})),
tooltip: {
enabled: false
},
legend: {
show: false
},
markers: {
size: 0
},
responsive: [{
breakpoint: undefined,
options: {},
}]
};
const formaters = dataMetrics.map((data) => FormaterForMetric(data));
return <Stack direction='row' spacing={3}
alignItems='center' sx={{padding: '0px 20px', width: '100%', backgroundColor: 'rgba(0,0,0,0.1)'}}
justifyContent={'space-around'}>
{dataMetrics && dataMetrics.map((dataMetric, di) =>
<Stack direction='column' justifyContent={'center'} alignItems={'center'} spacing={0} style={{
width: '60px',
}}>
<div style={{
fontWeight: 'bold',
fontSize: '110%',
whiteSpace: 'nowrap',
}}>{formaters[di](dataMetric.Values[dataMetric.Values.length - 1].Value)}</div>
<div>{(labels && labels[di]) || dataMetric.Label}</div>
</Stack>)}
<div style={{
margin: '-10px 0px -20px 10px',
flexGrow: 1,
}}>
<ReactApexChart options={chartOptions} series={series} type="line" height={90} />
</div>
</Stack>
}
const MiniPlotComponent = ({ metrics, labels }) => {
const memoizedComponent = useMemo(() => <_MiniPlotComponent metrics={metrics} labels={labels} />, [metrics]);
return memoizedComponent;
};
export default MiniPlotComponent;

View file

@ -27,7 +27,7 @@ import ReactApexChart from 'react-apexcharts';
import { FormaterForMetric, toUTC } from './utils'; import { FormaterForMetric, toUTC } from './utils';
const PlotComponent = ({ title, slot, data, SimpleDesign, withSelector, xAxis, zoom, setZoom }) => { const PlotComponent = ({ title, slot, data, SimpleDesign, withSelector, xAxis, zoom, setZoom, zoomDisabled }) => {
const theme = useTheme(); const theme = useTheme();
const { primary, secondary } = theme.palette.text; const { primary, secondary } = theme.palette.text;
const line = theme.palette.divider; const line = theme.palette.divider;
@ -127,8 +127,7 @@ const PlotComponent = ({ title, slot, data, SimpleDesign, withSelector, xAxis, z
title: { title: {
text: SimpleDesign ? '' : thisdata.Label, text: SimpleDesign ? '' : thisdata.Label,
}, },
min: zoom.yaxis && zoom.yaxis.min, max: thisdata.Max ? thisdata.Max : undefined,
max: zoom.yaxis && zoom.yaxis.max,
})), })),
grid: { grid: {
borderColor: line borderColor: line
@ -137,16 +136,16 @@ const PlotComponent = ({ title, slot, data, SimpleDesign, withSelector, xAxis, z
theme: theme.palette.mode, theme: theme.palette.mode,
}, },
chart: { chart: {
zoom: {
enabled: !zoomDisabled,
},
selection: {
enabled: !zoomDisabled,
},
events: { events: {
zoomed: function(chartContext, { xaxis, yaxis }) { zoomed: function(chartContext, { xaxis }) {
// Handle the zoom event here setZoom({ xaxis });
console.log('Zoomed:', xaxis, yaxis); }
setZoom({ xaxis, yaxis });
},
selection: function(chartContext, { xaxis, yaxis }) {
// Handle the selection event here
console.log('Selected:', xaxis, yaxis);
},
} }
} }
})); }));
@ -161,11 +160,9 @@ const PlotComponent = ({ title, slot, data, SimpleDesign, withSelector, xAxis, z
useEffect(() => { useEffect(() => {
// if different // if different
if(zoom && zoom.xaxis && zoom.yaxis && (!options.xaxis || !options.yaxis || !options.yaxis[0] || ( if(zoom && zoom.xaxis && (!options.xaxis || (
zoom.xaxis.min !== options.xaxis.min || zoom.xaxis.min !== options.xaxis.min ||
zoom.xaxis.max !== options.xaxis.max || zoom.xaxis.max !== options.xaxis.max
zoom.yaxis.min !== options.yaxis[0].min ||
zoom.yaxis.max !== options.yaxis[0].max
))){ ))){
setOptions((prevState) => ({ setOptions((prevState) => ({
...prevState, ...prevState,
@ -174,21 +171,16 @@ const PlotComponent = ({ title, slot, data, SimpleDesign, withSelector, xAxis, z
min: zoom.xaxis.min, min: zoom.xaxis.min,
max: zoom.xaxis.max, max: zoom.xaxis.max,
}, },
yaxis: prevState.yaxis.map((y, id) => ({
...y,
min: zoom.yaxis.min,
max: zoom.yaxis.max,
}))
})); }));
} }
}, [zoom]); }, [zoom]);
return <Grid item xs={12} md={7} lg={8} > return <>
<Grid container alignItems="center" > <Grid container alignItems="center">
<Grid item> <Grid item>
<Typography variant="h5">{title}</Typography> <Typography variant="h5">{title}</Typography>
</Grid> </Grid>
<Grid item > <Grid item>
{withSelector && {withSelector &&
<div style={{ marginLeft: 15 }}> <div style={{ marginLeft: 15 }}>
<TextField <TextField
@ -208,11 +200,12 @@ const PlotComponent = ({ title, slot, data, SimpleDesign, withSelector, xAxis, z
</Grid> </Grid>
</Grid> </Grid>
<MainCard content={false} sx={{ mt: 1.5 }} > <MainCard content={false} sx={{ mt: 1.5 }} >
<Box sx={{ pt: 1, pr: 2 }}> <Box sx={{ pt: 1, pr: 2}}>
<ReactApexChart options={options} series={series} type="area" height={450} /> <ReactApexChart options={options} series={series} type="area" height={450} />
</Box> </Box>
</MainCard> </MainCard>
</Grid> </>
} }
export default PlotComponent; export default PlotComponent;

View file

@ -21,7 +21,12 @@ import {
TableCell, TableCell,
TableContainer, TableContainer,
Table, Table,
TableBody TableBody,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions
} from '@mui/material'; } from '@mui/material';
import MainCard from '../../../components/MainCard'; import MainCard from '../../../components/MainCard';
@ -34,6 +39,7 @@ import { object } from 'prop-types';
import { FormaterForMetric } from './utils'; import { FormaterForMetric } from './utils';
import { set } from 'lodash'; import { set } from 'lodash';
import { DownOutlined, UpOutlined } from '@ant-design/icons'; import { DownOutlined, UpOutlined } from '@ant-design/icons';
import PlotComponent from './plot';
function formatDate(now, time) { function formatDate(now, time) {
// use as UTC // use as UTC
@ -128,6 +134,7 @@ const TableComponent = ({ title, data, displayMax, render, xAxis, slot, zoom}) =
const [orderBy, setOrderBy] = useState('name'); const [orderBy, setOrderBy] = useState('name');
const [headCells, setHeadCells] = useState([]); const [headCells, setHeadCells] = useState([]);
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
const [openModal, setOpenModal] = useState(false);
useEffect(() => { useEffect(() => {
let heads = {}; let heads = {};
@ -189,11 +196,13 @@ const TableComponent = ({ title, data, displayMax, render, xAxis, slot, zoom}) =
fnrows.push({ fnrows.push({
name, name,
[cat]: render ? render(item, v, formatter(v)) : formatter(v), [cat]: render ? render(item, v, formatter(v)) : formatter(v),
["__" + cat]: v ["__" + cat]: v,
"__key": [item.Key]
}); });
} else { } else {
fnrows.find((row) => row.name === name)[cat] = render ? render(item, v, formatter(v)) : formatter(v) fnrows.find((row) => row.name === name)[cat] = render ? render(item, v, formatter(v)) : formatter(v)
fnrows.find((row) => row.name === name)["__" + cat] = v fnrows.find((row) => row.name === name)["__" + cat] = v
fnrows.find((row) => row.name === name)["__key"].push(item.Key)
} }
}); });
@ -233,12 +242,41 @@ const TableComponent = ({ title, data, displayMax, render, xAxis, slot, zoom}) =
}, [data, slot, xAxis, zoom]); }, [data, slot, xAxis, zoom]);
return <Grid item xs={12} md={5} lg={4}> return <>
{openModal && <Dialog open={openModal} onClose={() => setOpenModal(false)} maxWidth="md" fullWidth={true}>
<DialogTitle>Detailed History</DialogTitle>
<DialogContent>
<DialogContentText style={{
}}>
<PlotComponent
data={data.filter((item) => {
if (!openModal) {
return false;
}
return openModal.includes(item.Key)
})}
xAxis={xAxis}
slot={slot}
zoom={zoom}
zoomDisabled={true}
/>
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => {
setOpenModal(false)
}}>Close</Button>
</DialogActions>
</Dialog>}
<Grid item xs={12} md={5} lg={4}>
<Grid container alignItems="center" justifyContent="space-between"> <Grid container alignItems="center" justifyContent="space-between">
<Grid item> <Grid item>
<Typography variant="h5">{title}</Typography> <Typography variant="h5">{title}</Typography>
</Grid> </Grid>
</Grid> </Grid>
<MainCard content={false} sx={{ mt: 1.5 }}> <MainCard content={false} sx={{ mt: 1.5 }}>
<Box> <Box>
<TableContainer <TableContainer
@ -278,6 +316,9 @@ const TableComponent = ({ title, data, displayMax, render, xAxis, slot, zoom}) =
tabIndex={-1} tabIndex={-1}
key={row.trackingNo} key={row.trackingNo}
selected={isItemSelected} selected={isItemSelected}
onClick={() => {
setOpenModal(row.__key);
}}
> >
{headCells.map((headCell) => { {headCells.map((headCell) => {
return <TableCell align={ return <TableCell align={
@ -293,6 +334,7 @@ const TableComponent = ({ title, data, displayMax, render, xAxis, slot, zoom}) =
</Box> </Box>
</MainCard> </MainCard>
</Grid> </Grid>
</>
} }
export default TableComponent; export default TableComponent;

View file

@ -4,13 +4,13 @@ export const simplifyNumber = (num) => {
num = Math.round(num * 100) / 100; num = Math.round(num * 100) / 100;
if (Math.abs(num) >= 1e12) { if (Math.abs(num) >= 1e12) {
return (num / 1e12).toFixed(1) + 'T'; // Convert to Millions return (num / 1e12).toFixed(1) + ' T'; // Convert to Millions
} else if (Math.abs(num) >= 1e9) { } else if (Math.abs(num) >= 1e9) {
return (num / 1e9).toFixed(1) + 'G'; // Convert to Millions return (num / 1e9).toFixed(1) + ' G'; // Convert to Millions
} else if (Math.abs(num) >= 1e6) { } else if (Math.abs(num) >= 1e6) {
return (num / 1e6).toFixed(1) + 'M'; // Convert to Millions return (num / 1e6).toFixed(1) + ' M'; // Convert to Millions
} else if (Math.abs(num) >= 1e3) { } else if (Math.abs(num) >= 1e3) {
return (num / 1e3).toFixed(1) + 'K'; // Convert to Thousands return (num / 1e3).toFixed(1) + ' K'; // Convert to Thousands
} else { } else {
return num.toString(); return num.toString();
} }
@ -18,12 +18,11 @@ export const simplifyNumber = (num) => {
export const FormaterForMetric = (metric, displayMax) => { export const FormaterForMetric = (metric, displayMax) => {
return (num) => { return (num) => {
if(!num) return 0;
if(metric.Scale) if(metric.Scale)
num /= metric.Scale; num /= metric.Scale;
num = simplifyNumber(num); num = simplifyNumber(num) + metric.Unit;
if(displayMax && metric.Max) { if(displayMax && metric.Max) {
num += ` / ${simplifyNumber(metric.Max)}` num += ` / ${simplifyNumber(metric.Max)}`

View file

@ -0,0 +1,234 @@
import { useEffect, useState } from 'react';
// material-ui
import {
Avatar,
AvatarGroup,
Box,
Button,
Grid,
List,
ListItemAvatar,
ListItemButton,
ListItemSecondaryAction,
ListItemText,
MenuItem,
Stack,
TextField,
Typography,
Alert,
LinearProgress,
CircularProgress
} from '@mui/material';
// project import
import OrdersTable from './OrdersTable';
import IncomeAreaChart from './IncomeAreaChart';
import MonthlyBarChart from './MonthlyBarChart';
import ReportAreaChart from './ReportAreaChart';
import SalesColumnChart from './SalesColumnChart';
import MainCard from '../../components/MainCard';
import AnalyticEcommerce from '../../components/cards/statistics/AnalyticEcommerce';
// assets
import { GiftOutlined, MessageOutlined, SettingOutlined } from '@ant-design/icons';
import avatar1 from '../../assets/images/users/avatar-1.png';
import avatar2 from '../../assets/images/users/avatar-2.png';
import avatar3 from '../../assets/images/users/avatar-3.png';
import avatar4 from '../../assets/images/users/avatar-4.png';
import IsLoggedIn from '../../isLoggedIn';
import * as API from '../../api';
import AnimateButton from '../../components/@extended/AnimateButton';
import PlotComponent from './components/plot';
import TableComponent from './components/table';
import { HomeBackground, TransparentHeader } from '../home';
import { formatDate } from './components/utils';
// avatar style
const avatarSX = {
width: 36,
height: 36,
fontSize: '1rem'
};
// action style
const actionSX = {
mt: 0.75,
ml: 1,
top: 'auto',
right: 'auto',
alignSelf: 'flex-start',
transform: 'none'
};
// sales report status
const status = [
{
value: 'today',
label: 'Today'
},
{
value: 'month',
label: 'This Month'
},
{
value: 'year',
label: 'This Year'
}
];
// ==============================|| DASHBOARD - DEFAULT ||============================== //
const ContainerMetrics = ({containerName}) => {
const [value, setValue] = useState('today');
const [slot, setSlot] = useState('latest');
const [zoom, setZoom] = useState({
xaxis: {}
});
const [coStatus, setCoStatus] = useState(null);
const [metrics, setMetrics] = useState(null);
const [isCreatingDB, setIsCreatingDB] = useState(false);
const resetZoom = () => {
setZoom({
xaxis: {}
});
}
const metricsKey = {
CPU: "cosmos.system.docker.cpu." + containerName,
RAM: "cosmos.system.docker.ram." + containerName,
NET_RX: "cosmos.system.docker.netRx." + containerName,
NET_TX: "cosmos.system.docker.netTx." + containerName,
};
const refreshMetrics = () => {
API.metrics.get([
"cosmos.system.docker.cpu",
"cosmos.system.docker.ram",
"cosmos.system.docker.netRx",
"cosmos.system.docker.netTx",
].map(c => c + "." + containerName)).then((res) => {
let finalMetrics = {};
if(res.data) {
res.data.forEach((metric) => {
finalMetrics[metric.Key] = metric;
});
setMetrics(finalMetrics);
}
setTimeout(refreshMetrics, 10000);
});
};
const refreshStatus = () => {
API.getStatus().then((res) => {
setCoStatus(res.data);
});
}
useEffect(() => {
refreshStatus();
refreshMetrics();
}, []);
let xAxis = [];
if(slot === 'latest') {
for(let i = 0; i < 100; i++) {
xAxis.unshift(i);
}
}
else if(slot === 'hourly') {
for(let i = 0; i < 48; i++) {
let now = new Date();
now.setHours(now.getHours() - i);
now.setMinutes(0);
now.setSeconds(0);
xAxis.unshift(formatDate(now, true));
}
} else if(slot === 'daily') {
for(let i = 0; i < 30; i++) {
let now = new Date();
now.setDate(now.getDate() - i);
xAxis.unshift(formatDate(now));
}
}
return (
<>
<IsLoggedIn />
{!metrics && <Box style={{
width: '100%',
height: '100%',
zIndex: 1000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginTop: '150px',
}}>
<CircularProgress
size={100}
/>
</Box>}
{metrics && <div style={{zIndex:2, position: 'relative'}}>
<Grid container rowSpacing={4.5} columnSpacing={2.75} >
<Grid item xs={12} sx={{ mb: -2.25 }}>
<Typography variant="h4">{containerName} Monitoring</Typography>
<Stack direction="row" alignItems="center" spacing={0} style={{marginTop: 10}}>
<Button
size="small"
onClick={() => {setSlot('latest'); resetZoom()}}
color={slot === 'latest' ? 'primary' : 'secondary'}
variant={slot === 'latest' ? 'outlined' : 'text'}
>
Latest
</Button>
<Button
size="small"
onClick={() => {setSlot('hourly'); resetZoom()}}
color={slot === 'hourly' ? 'primary' : 'secondary'}
variant={slot === 'hourly' ? 'outlined' : 'text'}
>
Hourly
</Button>
<Button
size="small"
onClick={() => {setSlot('daily'); resetZoom()}}
color={slot === 'daily' ? 'primary' : 'secondary'}
variant={slot === 'daily' ? 'outlined' : 'text'}
>
Daily
</Button>
{zoom.xaxis.min && <Button
size="small"
onClick={() => {
setZoom({
xaxis: {}
});
}}
color={'primary'}
variant={'outlined'}
>
Reset Zoom
</Button>}
</Stack>
</Grid>
<Grid item xs={12}>
<PlotComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title={'Resources'} data={[metrics[metricsKey.CPU], metrics[metricsKey.RAM]]}/>
</Grid>
<Grid item xs={12}>
<PlotComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title={'Network'} data={[metrics[metricsKey.NET_TX], metrics[metricsKey.NET_RX]]}/>
</Grid>
</Grid>
</div>}
</>
);
};
export default ContainerMetrics;

View file

@ -44,6 +44,7 @@ import PlotComponent from './components/plot';
import TableComponent from './components/table'; import TableComponent from './components/table';
import { HomeBackground, TransparentHeader } from '../home'; import { HomeBackground, TransparentHeader } from '../home';
import { formatDate } from './components/utils'; import { formatDate } from './components/utils';
import MiniPlotComponent from './components/mini-plot';
// avatar style // avatar style
const avatarSX = { const avatarSX = {
@ -85,8 +86,7 @@ const DashboardDefault = () => {
const [slot, setSlot] = useState('latest'); const [slot, setSlot] = useState('latest');
const [zoom, setZoom] = useState({ const [zoom, setZoom] = useState({
xaxis: {}, xaxis: {}
yaxis: {}
}); });
const [coStatus, setCoStatus] = useState(null); const [coStatus, setCoStatus] = useState(null);
@ -95,13 +95,12 @@ const DashboardDefault = () => {
const resetZoom = () => { const resetZoom = () => {
setZoom({ setZoom({
xaxis: {}, xaxis: {}
yaxis: {}
}); });
} }
const refreshMetrics = () => { const refreshMetrics = () => {
API.metrics.get().then((res) => { API.metrics.get(["cosmos.system.*"]).then((res) => {
let finalMetrics = {}; let finalMetrics = {};
if(res.data) { if(res.data) {
res.data.forEach((metric) => { res.data.forEach((metric) => {
@ -109,7 +108,6 @@ const DashboardDefault = () => {
}); });
setMetrics(finalMetrics); setMetrics(finalMetrics);
} }
setTimeout(refreshMetrics, 10000);
}); });
}; };
@ -121,7 +119,14 @@ const DashboardDefault = () => {
useEffect(() => { useEffect(() => {
refreshStatus(); refreshStatus();
refreshMetrics();
let interval = setInterval(() => {
refreshMetrics();
}, 10000);
return () => {
clearInterval(interval);
};
}, []); }, []);
let xAxis = []; let xAxis = [];
@ -199,8 +204,7 @@ const DashboardDefault = () => {
size="small" size="small"
onClick={() => { onClick={() => {
setZoom({ setZoom({
xaxis: {}, xaxis: {}
yaxis: {}
}); });
}} }}
color={'primary'} color={'primary'}
@ -230,14 +234,17 @@ const DashboardDefault = () => {
<Grid item md={8} sx={{ display: { sm: 'none', md: 'block', lg: 'none' } }} /> <Grid item md={8} sx={{ display: { sm: 'none', md: 'block', lg: 'none' } }} />
*/} */}
<Grid item xs={12} md={7} lg={8}>
<PlotComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title={'Resources'} data={[metrics["cosmos.system.cpu.0"], metrics["cosmos.system.ram"]]}/> <PlotComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title={'Resources'} data={[metrics["cosmos.system.cpu.0"], metrics["cosmos.system.ram"]]}/>
</Grid>
<TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Containers - Resources" data={ <TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Containers - Resources" data={
Object.keys(metrics).filter((key) => key.startsWith("cosmos.system.docker.cpu") || key.startsWith("cosmos.system.docker.ram")).map((key) => metrics[key]) Object.keys(metrics).filter((key) => key.startsWith("cosmos.system.docker.cpu") || key.startsWith("cosmos.system.docker.ram")).map((key) => metrics[key])
}/> }/>
<PlotComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title={'Network'} data={[metrics["cosmos.system.netTx"], metrics["cosmos.system.netRx"]]}/> <Grid item xs={12} md={7} lg={8}>
<PlotComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title={'Network'} data={[metrics["cosmos.system.netTx"], metrics["cosmos.system.netRx"]]}/>
</Grid>
<TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Containers - Network" data={ <TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Containers - Network" data={
Object.keys(metrics).filter((key) => key.startsWith("cosmos.system.docker.net")).map((key) => metrics[key]) Object.keys(metrics).filter((key) => key.startsWith("cosmos.system.docker.net")).map((key) => metrics[key])
@ -245,25 +252,31 @@ const DashboardDefault = () => {
<TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Disk Usage" displayMax={true} <TableComponent xAxis={xAxis} zoom={zoom} setZoom={setZoom} slot={slot} title="Disk Usage" displayMax={true}
render={(metric, value, formattedValue) => { render={(metric, value, formattedValue) => {
let percent = value / metric.Max * 100;
return <span> return <span>
{formattedValue} {formattedValue}
<LinearProgress variant="determinate" value={value / metric.Max * 100} /> <LinearProgress
variant="determinate"
color={percent > 95 ? 'error' : (percent > 75 ? 'warning' : 'info')}
value={percent} />
</span> </span>
}} }}
data={ data={
Object.keys(metrics).filter((key) => key.startsWith("cosmos.system.disk")).map((key) => metrics[key]) Object.keys(metrics).filter((key) => key.startsWith("cosmos.system.disk")).map((key) => metrics[key])
}/> }/>
<PlotComponent <Grid item xs={12} md={7} lg={8}>
zoom={zoom} setZoom={setZoom} <PlotComponent
xAxis={xAxis} zoom={zoom} setZoom={setZoom}
slot={slot} xAxis={xAxis}
title={'Temperature'} slot={slot}
withSelector={'cosmos.system.temp.all'} title={'Temperature'}
SimpleDesign withSelector={'cosmos.system.temp.all'}
data={Object.keys(metrics).filter((key) => key.startsWith("cosmos.system.temp")).map((key) => metrics[key])} SimpleDesign
/> data={Object.keys(metrics).filter((key) => key.startsWith("cosmos.system.temp")).map((key) => metrics[key])}
/>
</Grid>
{/* {/*
<Grid item xs={12} md={7} lg={8}> <Grid item xs={12} md={7} lg={8}>
<Grid container alignItems="center" justifyContent="space-between"> <Grid container alignItems="center" justifyContent="space-between">

View file

@ -17,6 +17,7 @@ import DockerContainerSetup from './setup';
import NetworkContainerSetup from './network'; import NetworkContainerSetup from './network';
import VolumeContainerSetup from './volumes'; import VolumeContainerSetup from './volumes';
import DockerTerminal from './terminal'; import DockerTerminal from './terminal';
import ContainerMetrics from '../../dashboard/containerMetrics';
const ContainerIndex = () => { const ContainerIndex = () => {
const { containerName } = useParams(); const { containerName } = useParams();
@ -59,6 +60,10 @@ const ContainerIndex = () => {
title: 'Logs', title: 'Logs',
children: <Logs containerInfo={container} config={config}/> children: <Logs containerInfo={container} config={config}/>
}, },
{
title: 'Monitoring',
children: <ContainerMetrics containerName={containerName}/>
},
{ {
title: 'Terminal', title: 'Terminal',
children: <DockerTerminal refresh={refreshContainer} containerInfo={container} config={config}/> children: <DockerTerminal refresh={refreshContainer} containerInfo={container} config={config}/>

View file

@ -9,6 +9,7 @@ import * as API from '../../../api';
import RestartModal from '../../config/users/restart'; import RestartModal from '../../config/users/restart';
import GetActions from '../actionBar'; import GetActions from '../actionBar';
import { ServAppIcon } from '../../../utils/servapp-icon'; import { ServAppIcon } from '../../../utils/servapp-icon';
import MiniPlotComponent from '../../dashboard/components/mini-plot';
const info = { const info = {
backgroundColor: 'rgba(0, 0, 0, 0.1)', backgroundColor: 'rgba(0, 0, 0, 0.1)',
@ -168,6 +169,17 @@ const ContainerOverview = ({ containerInfo, config, refresh, updatesAvailable, s
}} }}
/> />
</div> </div>
<strong><NodeExpandOutlined /> Monitoring</strong>
<div style={{ width: '90%' }}>
<MiniPlotComponent metrics={[
"cosmos.system.docker.cpu." + Name.replace('/', ''),
"cosmos.system.docker.ram." + Name.replace('/', ''),
]} labels={["CPU", "RAM"]}/>
<MiniPlotComponent metrics={[
"cosmos.system.docker.netTx." + Name.replace('/', ''),
"cosmos.system.docker.netRx." + Name.replace('/', ''),
]} labels={["NTX", "NRX"]}/>
</div>
</Stack> </Stack>
</Stack> </Stack>
</MainCard> </MainCard>

View file

@ -20,6 +20,7 @@ import ResponsiveButton from '../../components/responseiveButton';
import DockerComposeImport from './containers/docker-compose'; import DockerComposeImport from './containers/docker-compose';
import { ContainerNetworkWarning } from '../../components/containers'; import { ContainerNetworkWarning } from '../../components/containers';
import { ServAppIcon } from '../../utils/servapp-icon'; import { ServAppIcon } from '../../utils/servapp-icon';
import MiniPlotComponent from '../dashboard/components/mini-plot';
const Item = styled(Paper)(({ theme }) => ({ const Item = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff', backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
@ -296,6 +297,12 @@ const ServApps = () => {
{/* } */} {/* } */}
</Stack> </Stack>
</Stack> </Stack>
<div>
<MiniPlotComponent metrics={[
"cosmos.system.docker.cpu." + app.Names[0].replace('/', ''),
"cosmos.system.docker.ram." + app.Names[0].replace('/', ''),
]} labels={["CPU", "RAM"]}/>
</div>
<div> <div>
<Link to={`/cosmos-ui/servapps/containers/${app.Names[0].replace('/', '')}`}> <Link to={`/cosmos-ui/servapps/containers/${app.Names[0].replace('/', '')}`}>
<Button variant="outlined" color="primary" fullWidth>Details</Button> <Button variant="outlined" color="primary" fullWidth>Details</Button>

View file

@ -42,7 +42,7 @@ const MainRoutes = {
element: <HomePage /> element: <HomePage />
}, },
{ {
path: '/cosmos-ui/dashboard', path: '/cosmos-ui/monitoring',
element: <DashboardDefault /> element: <DashboardDefault />
}, },
{ {

View file

@ -3,7 +3,7 @@ import { createSlice } from '@reduxjs/toolkit';
// initial state // initial state
const initialState = { const initialState = {
openItem: ['dashboard'], openItem: ['home'],
openComponent: 'buttons', openComponent: 'buttons',
drawerOpen: false, drawerOpen: false,
componentDrawerOpen: true componentDrawerOpen: true

View file

@ -1,6 +1,6 @@
{ {
"name": "cosmos-server", "name": "cosmos-server",
"version": "0.12.0-unstable23", "version": "0.12.0-unstable24",
"description": "", "description": "",
"main": "test-server.js", "main": "test-server.js",
"bugs": { "bugs": {

View file

@ -2,10 +2,12 @@ package metrics
import ( import (
"time" "time"
"fmt"
"strings"
"github.com/jasonlvhit/gocron" "github.com/jasonlvhit/gocron"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo"
"github.com/azukaar/cosmos-server/src/utils" "github.com/azukaar/cosmos-server/src/utils"
) )
@ -30,13 +32,14 @@ type DataDefDB struct {
Key string Key string
AggloType string AggloType string
Scale int Scale int
Unit string
} }
func AggloMetrics() []DataDefDB { func AggloMetrics(metricsList []string) []DataDefDB {
lock <- true lock <- true
defer func() { <-lock }() defer func() { <-lock }()
utils.Log("Metrics: Agglomeration started") utils.Log("Metrics: Agglomeration of metrics")
utils.Debug("Time: " + time.Now().String()) utils.Debug("Time: " + time.Now().String())
@ -47,8 +50,30 @@ func AggloMetrics() []DataDefDB {
} }
// get all metrics from database // get all metrics from database
findOpts := map[string]interface{}{
}
// If metricsList is not empty, filter by metrics with wildcard matching
if len(metricsList) > 0 {
// Convert wildcards to regex and store them in an array
var regexPatterns []bson.M
for _, metric := range metricsList {
if strings.Contains(metric, "*") {
// Convert wildcard to regex. Replace * with .*
regexPattern := "^" + strings.ReplaceAll(metric, "*", ".*")
regexPatterns = append(regexPatterns, bson.M{"Key": bson.M{"$regex": regexPattern}})
} else {
// If there's no wildcard, match the metric directly
regexPatterns = append(regexPatterns, bson.M{"Key": metric})
}
}
// Use the $or operator to match any of the patterns
findOpts["$or"] = regexPatterns
}
var metrics []DataDefDB var metrics []DataDefDB
cursor, err := c.Find(nil, map[string]interface{}{}) cursor, err := c.Find(nil, findOpts)
if err != nil { if err != nil {
utils.Error("Metrics: Error fetching metrics", err) utils.Error("Metrics: Error fetching metrics", err)
return []DataDefDB{} return []DataDefDB{}
@ -162,21 +187,36 @@ func CommitAggl(metrics []DataDefDB) {
defer func() { <-lock }() defer func() { <-lock }()
utils.Log("Metrics: Agglomeration done. Saving to DB") utils.Log("Metrics: Agglomeration done. Saving to DB")
c, errCo := utils.GetCollection(utils.GetRootAppId(), "metrics") c, errCo := utils.GetCollection(utils.GetRootAppId(), "metrics")
if errCo != nil { if errCo != nil {
utils.Error("Metrics - Database Connect", errCo) utils.Error("Metrics - Database Connect", errCo)
return return
} }
// save metrics chunkSize := 100
for _, metric := range metrics {
options := options.Update().SetUpsert(true)
_, err := c.UpdateOne(nil, bson.M{"Key": metric.Key}, bson.M{"$set": bson.M{"Values": metric.Values, "ValuesAggl": metric.ValuesAggl}}, options) for i := 0; i < len(metrics); i += chunkSize {
end := i + chunkSize
if end > len(metrics) {
end = len(metrics)
}
chunk := metrics[i:end]
models := []mongo.WriteModel{}
for _, metric := range chunk {
update := mongo.NewUpdateOneModel().
SetFilter(bson.M{"Key": metric.Key}).
SetUpdate(bson.M{"$set": bson.M{"Values": metric.Values, "ValuesAggl": metric.ValuesAggl}}).
SetUpsert(true)
models = append(models, update)
}
_, err := c.BulkWrite(nil, models)
if err != nil { if err != nil {
utils.Error("Metrics: Error saving metrics", err) utils.Error(fmt.Sprintf("Metrics: Error saving metrics chunk starting at index %d", i), err)
return return
} }
} }
@ -185,7 +225,11 @@ func CommitAggl(metrics []DataDefDB) {
} }
func AggloAndCommitMetrics() { func AggloAndCommitMetrics() {
CommitAggl(AggloMetrics()) if utils.GetMainConfig().MonitoringDisabled {
return
}
CommitAggl(AggloMetrics([]string{}))
} }
func InitAggl() { func InitAggl() {

View file

@ -3,6 +3,7 @@ package metrics
import ( import (
"net/http" "net/http"
"encoding/json" "encoding/json"
"strings"
"github.com/azukaar/cosmos-server/src/utils" "github.com/azukaar/cosmos-server/src/utils"
) )
@ -12,10 +13,23 @@ func API_GetMetrics(w http.ResponseWriter, req *http.Request) {
return return
} }
//get query string "metrics"
query := req.URL.Query()
metrics := query.Get("metrics")
// split by comma
metricsList := []string{}
if metrics != "" {
metricsList = strings.Split(metrics, ",")
}
if(req.Method == "GET") { if(req.Method == "GET") {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{ json.NewEncoder(w).Encode(map[string]interface{}{
"status": "OK", "status": "OK",
"data": AggloMetrics(), "data": AggloMetrics(metricsList),
}) })
} else { } else {
utils.Error("MetricsGet: Method not allowed" + req.Method, nil) utils.Error("MetricsGet: Method not allowed" + req.Method, nil)

View file

@ -17,6 +17,8 @@ type DataDef struct {
AggloType string AggloType string
SetOperation string SetOperation string
Scale int Scale int
Unit string
Decumulate bool
} }
type DataPush struct { type DataPush struct {
@ -29,6 +31,8 @@ type DataPush struct {
AvgIndex int AvgIndex int
AggloType string AggloType string
Scale int Scale int
Unit string
Decumulate bool
} }
var dataBuffer = map[string]DataPush{} var dataBuffer = map[string]DataPush{}
@ -44,6 +48,8 @@ func MergeMetric(SetOperation string, currentValue int, newValue int, avgIndex i
} else { } else {
return currentValue return currentValue
} }
} else if SetOperation == "sum" {
return currentValue + newValue
} else if SetOperation == "min" { } else if SetOperation == "min" {
if newValue < currentValue { if newValue < currentValue {
return newValue return newValue
@ -102,6 +108,7 @@ func SaveMetrics() {
"Label": dp.Label, "Label": dp.Label,
"AggloType": dp.AggloType, "AggloType": dp.AggloType,
"Scale": scale, "Scale": scale,
"Unit": dp.Unit,
}, },
} }
@ -132,8 +139,11 @@ func ModuloTime(start time.Time, modulo time.Duration) time.Time {
return time.Unix(0, roundedElapsed) return time.Unix(0, roundedElapsed)
} }
var lastInserted = map[string]int{}
func PushSetMetric(key string, value int, def DataDef) { func PushSetMetric(key string, value int, def DataDef) {
go func() { go func() {
originalValue := value
key = "cosmos." + key key = "cosmos." + key
date := ModuloTime(time.Now(), def.Period) date := ModuloTime(time.Now(), def.Period)
cacheKey := key + date.String() cacheKey := key + date.String()
@ -141,9 +151,20 @@ func PushSetMetric(key string, value int, def DataDef) {
lock <- true lock <- true
defer func() { <-lock }() defer func() { <-lock }()
if def.Decumulate {
if lastInserted[key] != 0 {
value = value - lastInserted[key]
} else {
value = 0
}
}
if dp, ok := dataBuffer[cacheKey]; ok { if dp, ok := dataBuffer[cacheKey]; ok {
value = MergeMetric(def.SetOperation, dp.Value, value, dp.AvgIndex)
dp.Max = def.Max dp.Max = def.Max
dp.Value = MergeMetric(def.SetOperation, dp.Value, value, dp.AvgIndex) dp.Value = value
if def.SetOperation == "avg" { if def.SetOperation == "avg" {
dp.AvgIndex++ dp.AvgIndex++
} }
@ -159,18 +180,29 @@ func PushSetMetric(key string, value int, def DataDef) {
Label: def.Label, Label: def.Label,
AggloType: def.AggloType, AggloType: def.AggloType,
Scale: def.Scale, Scale: def.Scale,
Unit: def.Unit,
} }
} }
lastInserted[key] = originalValue
}() }()
} }
func Run() { func Run() {
utils.Debug("Metrics - Run") utils.Debug("Metrics - Run")
nextTime := ModuloTime(time.Now().Add(time.Second*30), time.Second*30) nextTime := ModuloTime(time.Now().Add(time.Second*30), time.Second*30)
nextTime = nextTime.Add(time.Second * 2) nextTime = nextTime.Add(time.Second * 2)
utils.Debug("Metrics - Next run at " + nextTime.String()) utils.Debug("Metrics - Next run at " + nextTime.String())
if utils.GetMainConfig().MonitoringDisabled {
time.AfterFunc(nextTime.Sub(time.Now()), func() {
Run()
})
return
}
time.AfterFunc(nextTime.Sub(time.Now()), func() { time.AfterFunc(nextTime.Sub(time.Now()), func() {
go func() { go func() {
GetSystemMetrics() GetSystemMetrics()
@ -182,8 +214,12 @@ func Run() {
} }
func Init() { func Init() {
lastInserted = map[string]int{}
InitAggl() InitAggl()
Run() Run()
go GetSystemMetrics() if !utils.GetMainConfig().MonitoringDisabled {
go GetSystemMetrics()
}
} }

View file

@ -60,6 +60,7 @@ func GetSystemMetrics() {
Period: time.Second * 30, Period: time.Second * 30,
Label: "CPU " + strconv.Itoa(i), Label: "CPU " + strconv.Itoa(i),
AggloType: "avg", AggloType: "avg",
Unit: "%",
}) })
} }
} }
@ -78,6 +79,7 @@ func GetSystemMetrics() {
Period: time.Second * 30, Period: time.Second * 30,
Label: "RAM", Label: "RAM",
AggloType: "avg", AggloType: "avg",
Unit: "B",
}) })
// Get Network Usage // Get Network Usage
@ -92,65 +94,40 @@ func GetSystemMetrics() {
Max: 0, Max: 0,
Period: time.Second * 30, Period: time.Second * 30,
Label: "Network Received", Label: "Network Received",
AggloType: "avg", SetOperation: "max",
AggloType: "sum",
Decumulate: true,
Unit: "B",
}) })
PushSetMetric("system.netTx", int(netIO[0].BytesSent), DataDef{ PushSetMetric("system.netTx", int(netIO[0].BytesSent), DataDef{
Max: 0, Max: 0,
Period: time.Second * 30, Period: time.Second * 30,
Label: "Network Sent", Label: "Network Sent",
AggloType: "avg", SetOperation: "max",
AggloType: "sum",
Decumulate: true,
Unit: "B",
}) })
PushSetMetric("system.netErr", int(netIO[0].Errin + netIO[0].Errout), DataDef{ PushSetMetric("system.netErr", int(netIO[0].Errin + netIO[0].Errout), DataDef{
Max: 0, Max: 0,
Period: time.Second * 30, Period: time.Second * 30,
Label: "Network Errors", Label: "Network Errors",
AggloType: "avg", SetOperation: "max",
AggloType: "sum",
Decumulate: true,
}) })
PushSetMetric("system.netDrop", int(netIO[0].Dropin + netIO[0].Dropout), DataDef{ PushSetMetric("system.netDrop", int(netIO[0].Dropin + netIO[0].Dropout), DataDef{
Max: 0, Max: 0,
Period: time.Second * 30, Period: time.Second * 30,
Label: "Network Drops", Label: "Network Drops",
AggloType: "avg", SetOperation: "max",
AggloType: "sum",
Decumulate: true,
}) })
// docker stats
dockerStats, err := docker.StatsAll()
if err != nil {
utils.Error("Metrics - Error fetching Docker stats:", err)
return
}
for _, ds := range dockerStats {
PushSetMetric("system.docker.cpu." + ds.Name, int(ds.CPUUsage), DataDef{
Max: 100,
Period: time.Second * 30,
Label: "Docker CPU " + ds.Name,
AggloType: "avg",
Scale: 1000,
})
PushSetMetric("system.docker.ram." + ds.Name, int(ds.MemUsage), DataDef{
Max: 100,
Period: time.Second * 30,
Label: "Docker RAM " + ds.Name,
AggloType: "avg",
})
PushSetMetric("system.docker.netRx." + ds.Name, int(ds.NetworkRx), DataDef{
Max: 0,
Period: time.Second * 30,
Label: "Docker Network Received " + ds.Name,
AggloType: "avg",
})
PushSetMetric("system.docker.netTx." + ds.Name, int(ds.NetworkTx), DataDef{
Max: 0,
Period: time.Second * 30,
Label: "Docker Network Sent " + ds.Name,
AggloType: "avg",
})
}
// Get Disk Usage // Get Disk Usage
parts, err := disk.PartitionsWithContext(ctx, true) parts, err := disk.PartitionsWithContext(ctx, true)
if err != nil { if err != nil {
@ -202,6 +179,7 @@ func GetSystemMetrics() {
Max: 0, Max: 0,
Period: time.Second * 30, Period: time.Second * 30,
Label: "Temperature " + temp.SensorKey, Label: "Temperature " + temp.SensorKey,
Unit: "°C",
}) })
} }
} }
@ -213,4 +191,48 @@ func GetSystemMetrics() {
Label: "Temperature - All", Label: "Temperature - All",
}) })
} }
// docker stats
dockerStats, err := docker.StatsAll()
if err != nil {
utils.Error("Metrics - Error fetching Docker stats:", err)
return
}
for _, ds := range dockerStats {
PushSetMetric("system.docker.cpu." + ds.Name, int(ds.CPUUsage), DataDef{
Max: 100,
Period: time.Second * 30,
Label: "Docker CPU " + ds.Name,
AggloType: "avg",
Scale: 1000,
Unit: "%",
})
PushSetMetric("system.docker.ram." + ds.Name, int(ds.MemUsage), DataDef{
Max: 0,
Period: time.Second * 30,
Label: "Docker RAM " + ds.Name,
AggloType: "avg",
Unit: "B",
})
PushSetMetric("system.docker.netRx." + ds.Name, int(ds.NetworkRx), DataDef{
Max: 0,
Period: time.Second * 30,
Label: "Docker Network Received " + ds.Name,
SetOperation: "max",
AggloType: "sum",
Decumulate: true,
Unit: "B",
})
PushSetMetric("system.docker.netTx." + ds.Name, int(ds.NetworkTx), DataDef{
Max: 0,
Period: time.Second * 30,
Label: "Docker Network Sent " + ds.Name,
SetOperation: "max",
AggloType: "sum",
Decumulate: true,
Unit: "B",
})
}
} }

View file

@ -91,6 +91,7 @@ type Config struct {
HomepageConfig HomepageConfig HomepageConfig HomepageConfig
ThemeConfig ThemeConfig ThemeConfig ThemeConfig
ConstellationConfig ConstellationConfig ConstellationConfig ConstellationConfig
MonitoringDisabled bool
} }
type HomepageConfig struct { type HomepageConfig struct {