diff --git a/src/fireedge/etc/sunstone/admin/host-tab.yaml b/src/fireedge/etc/sunstone/admin/host-tab.yaml index 1f54a0d600a..b82574b9c0e 100644 --- a/src/fireedge/etc/sunstone/admin/host-tab.yaml +++ b/src/fireedge/etc/sunstone/admin/host-tab.yaml @@ -62,6 +62,8 @@ info-tabs: vms: enabled: true + graphs: + enabled: true wild: enabled: true zombies: @@ -84,4 +86,4 @@ dialogs: not_on: - kvm - lxc - - firecracker \ No newline at end of file + - firecracker diff --git a/src/fireedge/src/client/components/Charts/Chartist.js b/src/fireedge/src/client/components/Charts/Chartist.js index 9bff677ff17..7f635d3f634 100644 --- a/src/fireedge/src/client/components/Charts/Chartist.js +++ b/src/fireedge/src/client/components/Charts/Chartist.js @@ -19,6 +19,7 @@ import { JSXElementConstructor, useMemo } from 'react' import { ArgumentScale, ValueScale } from '@devexpress/dx-react-chart' import { ArgumentAxis, + Legend, Chart, LineSeries, ValueAxis, @@ -78,9 +79,12 @@ const calculateDerivative = (data) => * @param {string} props.name - Chartist name * @param {string} props.filter - Chartist filter * @param {string} props.x - Chartist X - * @param {string} props.y - Chartist X + * @param {Array|string} props.y - Chartist Y * @param {Function} props.interpolationY - Chartist interpolation Y * @param {boolean} props.derivative - Display delta values + * @param {boolean} props.enableLegend - Enable graph legend + * @param {Array} props.legendNames - List of legend names + * @param {Array} props.lineColors - Array of line colors * @returns {JSXElementConstructor} Chartist component */ const Chartist = ({ @@ -91,23 +95,28 @@ const Chartist = ({ y = '', interpolationY = (value) => value, derivative = false, + enableLegend = false, + legendNames = [], + lineColors = [], }) => { const classes = useStyles() const dataChart = filter?.length - ? useMemo( - () => - data - ?.filter( - (point) => - !!Object.keys(point).find((key) => filter.includes(key)) - ) - .map((point, i) => ({ - x: x === 'TIMESTAMP' ? new Date(+point[x] * 1000) : +point[x], - y: Math.round(+point[y]), - })), - [data] - ) + ? useMemo(() => { + let filteredData = data + if (filter.length) { + filteredData = data.filter((point) => + Object.keys(point).some((key) => filter.includes(key)) + ) + } + + return filteredData.map((point) => ({ + x: x === 'TIMESTAMP' ? new Date(+point[x] * 1000) : +point[x], + ...(Array.isArray(y) + ? Object.fromEntries(y.map((pt) => [pt, Math.round(+point[pt])])) + : { y: Math.round(+point[y]) }), + })) + }, [data]) : [] const processedData = derivative ? calculateDerivative(dataChart) : dataChart @@ -134,7 +143,21 @@ const Chartist = ({ interpolationY} /> - + {Array.isArray(y) ? ( + y.map((yValue, index) => ( + + )) + ) : ( + + )} + {enableLegend && } + )} @@ -149,9 +172,15 @@ Chartist.propTypes = { filter: PropTypes.arrayOf(PropTypes.string), data: PropTypes.array, x: PropTypes.string, - y: PropTypes.string, + y: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.string, + ]), interpolationY: PropTypes.func, derivative: PropTypes.bool, + enableLegend: PropTypes.bool, + legendNames: PropTypes.arrayOf(PropTypes.string), + lineColors: PropTypes.arrayOf(PropTypes.string), } Chartist.displayName = 'Chartist' diff --git a/src/fireedge/src/client/components/Charts/MultiChart/helpers/scripts/dataProcessing.js b/src/fireedge/src/client/components/Charts/MultiChart/helpers/scripts/dataProcessing.js index 1cde6870d63..62f78451cd9 100644 --- a/src/fireedge/src/client/components/Charts/MultiChart/helpers/scripts/dataProcessing.js +++ b/src/fireedge/src/client/components/Charts/MultiChart/helpers/scripts/dataProcessing.js @@ -27,6 +27,15 @@ * @param {string} groupBy - The attribute by which data should be grouped (e.g., 'NAME', 'OID'). * @returns {Array} An array of processed data items, each structured with properties for every metric from every dataset. */ +import _ from 'lodash' + +/** + * @param {Array} uniqueGroups - Groups to sort by + * @param {Array} datasets - All datasets in pool + * @param {Array} visibleDatasetIDs - Dataset ID's to render + * @param {string} groupBy - Group data by key + * @returns {object} - Processed dataset + */ export const processDataForChart = ( uniqueGroups, datasets, @@ -94,6 +103,7 @@ const findFirstArray = (obj, depth = 0, maxDepth = Infinity) => { * @param {Array} metricKeys - An array of keys to aggregate for the metrics. * @param {Function} labelingFunction - A function to generate the label for the dataset. * @param {number} depth - Depth of recursion when finding data array. + * @param {string} dataArrayPath - Path to data array in API response * @returns {object} - The transformed dataset. */ export const transformApiResponseToDataset = ( @@ -101,9 +111,12 @@ export const transformApiResponseToDataset = ( keyMap, metricKeys, labelingFunction, - depth = 0 + depth = 0, + dataArrayPath ) => { - const dataArray = findFirstArray(apiResponse, depth) + const dataArray = dataArrayPath + ? _.get(apiResponse, dataArrayPath) + : findFirstArray(apiResponse, depth) const flattenObject = (obj, prefix = '') => Object.keys(obj).reduce((acc, k) => { diff --git a/src/fireedge/src/client/components/Tabs/Host/Graphs/index.js b/src/fireedge/src/client/components/Tabs/Host/Graphs/index.js new file mode 100644 index 00000000000..b4d502c518b --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Host/Graphs/index.js @@ -0,0 +1,79 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { Grid } from '@mui/material' +import { prettyBytes } from 'client/utils' +import PropTypes from 'prop-types' +import { ReactElement } from 'react' +import { Chartist } from 'client/components/Charts' +import { T } from 'client/constants' +import { useGetHostMonitoringQuery } from 'client/features/OneApi/host' + +/** + * Renders the host Graph tab. + * + * @param {object} props - Props + * @param {string} props.id - Host id + * @returns {ReactElement} Graphs tab + */ +const HostGraphTab = ({ id }) => { + const { + data: { MONITORING_DATA: { MONITORING: monitoring = [] } = {} } = {}, + } = useGetHostMonitoringQuery(id) || {} + + const cpuMemoryData = monitoring?.map(({ TIMESTAMP, CAPACITY }) => ({ + TIMESTAMP, + ...CAPACITY, + })) + + return ( + + + + + + prettyBytes(value)} + /> + + + ) +} + +HostGraphTab.propTypes = { + tabProps: PropTypes.object, + id: PropTypes.string, +} + +HostGraphTab.displayName = 'HostGraphTab' + +export default HostGraphTab diff --git a/src/fireedge/src/client/components/Tabs/Host/PCI/index.js b/src/fireedge/src/client/components/Tabs/Host/PCI/index.js new file mode 100644 index 00000000000..3d5adde607e --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Host/PCI/index.js @@ -0,0 +1,136 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import PropTypes from 'prop-types' +import { ReactElement, useState, useCallback } from 'react' +import { MultiChart } from 'client/components/Charts' +import { useGetHostQuery } from 'client/features/OneApi/host' +import { transformApiResponseToDataset } from 'client/components/Charts/MultiChart/helpers/scripts' +import { Select, MenuItem, InputLabel, FormControl, Box } from '@mui/material' +import _ from 'lodash' + +const keyMap = { + ADDRESS: 'fullAddress', + SHORT_ADDRESS: 'shortAddress', + TYPE: 'type', + DEVICE_NAME: 'deviceName', + VENDOR_NAME: 'vendor', + VMID: 'vmId', +} + +const DataGridColumns = [ + { field: 'vmId', headerName: 'VM', flex: 1, type: 'number' }, + { field: 'shortAddress', headerName: 'Address', flex: 1 }, + { field: 'type', headerName: 'Type', flex: 1 }, + { field: 'deviceName', headerName: 'Name', flex: 1 }, + { field: 'vendor', headerName: 'Vendor', flex: 1, type: 'number' }, +] + +const metricKeys = ['vmId'] + +const metricNames = { + shortAddress: 'PCI Address', + type: 'Type', + deviceName: 'Name', + vmId: 'VMs', +} + +const labelingFunc = () => `PCI` + +/** + * Renders mainly information tab. + * + * @param {object} props - Props + * @param {string} props.id - Host id + * @returns {ReactElement} Information tab + */ +const HostPciTab = ({ id }) => { + const [chartType, setChartType] = useState('table') + const { data } = useGetHostQuery({ id }) + + const pciDevicesPath = 'HOST_SHARE.PCI_DEVICES.PCI' + + const transformedDataset = transformApiResponseToDataset( + data, + keyMap, + metricKeys, + labelingFunc, + 0, + pciDevicesPath + ) + + const handleChartTypeChange = useCallback((event) => { + const newChartType = event.target.value + setChartType(newChartType) + }, []) + + transformedDataset.dataset.data.forEach((dev, idx) => { + if (+dev.vmId < 0) { + _.set(transformedDataset.dataset.data, [idx, 'vmId'], '0') + } + }) + + return ( + <> + + + Chart Type + + + + + + ) +} + +HostPciTab.propTypes = { + tabProps: PropTypes.object, + id: PropTypes.string, +} + +HostPciTab.displayName = 'HostPciTab' + +export default HostPciTab diff --git a/src/fireedge/src/client/components/Tabs/Host/index.js b/src/fireedge/src/client/components/Tabs/Host/index.js index 89bd9fd9e4e..719dfb4f7c2 100644 --- a/src/fireedge/src/client/components/Tabs/Host/index.js +++ b/src/fireedge/src/client/components/Tabs/Host/index.js @@ -24,6 +24,8 @@ import { getAvailableInfoTabs } from 'client/models/Helper' import Tabs from 'client/components/Tabs' import Info from 'client/components/Tabs/Host/Info' +import Graph from 'client/components/Tabs/Host/Graphs' +import PCI from 'client/components/Tabs/Host/PCI' import Numa from 'client/components/Tabs/Host/Numa' import Vms from 'client/components/Tabs/Host/Vms' import Wilds from 'client/components/Tabs/Host/Wilds' @@ -32,9 +34,11 @@ import Zombies from 'client/components/Tabs/Host/Zombies' const getTabComponent = (tabName) => ({ info: Info, + graphs: Graph, vms: Vms, wild: Wilds, numa: Numa, + pci: PCI, zombies: Zombies, }[tabName]) diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 164dc2ee46a..d1594c79619 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -1390,6 +1390,10 @@ module.exports = { Overcommitment: 'Overcommitment', /* Host schema - template */ ISOLCPUS: 'Isolated CPUS', + FreeCPU: 'Free CPU', + UsedCPU: 'Used CPU', + FreeMemory: 'Free Memory', + UsedMemory: 'Used memory', TemplateToIsolateCpus: 'Comma separated list of CPU IDs that will be isolated from the NUMA scheduler',