[release] v0.12.0-unstable24
This commit is contained in:
parent
d247708d73
commit
10fe2d7f25
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
|
|
187
client/src/pages/dashboard/components/mini-plot.jsx
Normal file
187
client/src/pages/dashboard/components/mini-plot.jsx
Normal 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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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)}`
|
||||||
|
|
234
client/src/pages/dashboard/containerMetrics.jsx
Normal file
234
client/src/pages/dashboard/containerMetrics.jsx
Normal 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;
|
|
@ -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();
|
||||||
|
|
||||||
|
let interval = setInterval(() => {
|
||||||
refreshMetrics();
|
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])
|
||||||
}/>
|
}/>
|
||||||
|
|
||||||
|
<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"]]}/>
|
<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,15 +252,20 @@ 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])
|
||||||
}/>
|
}/>
|
||||||
|
|
||||||
|
<Grid item xs={12} md={7} lg={8}>
|
||||||
<PlotComponent
|
<PlotComponent
|
||||||
zoom={zoom} setZoom={setZoom}
|
zoom={zoom} setZoom={setZoom}
|
||||||
xAxis={xAxis}
|
xAxis={xAxis}
|
||||||
|
@ -263,6 +275,7 @@ const DashboardDefault = () => {
|
||||||
SimpleDesign
|
SimpleDesign
|
||||||
data={Object.keys(metrics).filter((key) => key.startsWith("cosmos.system.temp")).map((key) => metrics[key])}
|
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}>
|
||||||
|
|
|
@ -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}/>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -42,7 +42,7 @@ const MainRoutes = {
|
||||||
element: <HomePage />
|
element: <HomePage />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/cosmos-ui/dashboard',
|
path: '/cosmos-ui/monitoring',
|
||||||
element: <DashboardDefault />
|
element: <DashboardDefault />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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{}
|
||||||
|
@ -169,14 +194,29 @@ func CommitAggl(metrics []DataDefDB) {
|
||||||
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() {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,8 +180,11 @@ 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
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,8 +193,16 @@ func 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()
|
||||||
|
|
||||||
|
if !utils.GetMainConfig().MonitoringDisabled {
|
||||||
go GetSystemMetrics()
|
go GetSystemMetrics()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue