mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-01-25 17:41:21 +01:00
Implement rest of Performance tab (graphs) in React
Adds all graphs and their respective tabs. Extra: - Zero baselined charts where it made sense - Increased performance graph card height to improve readability Affects issues: - Close #2270
This commit is contained in:
parent
cc217b5ccd
commit
a43a2ae581
@ -94,7 +94,9 @@ public enum HtmlLang implements Lang {
|
|||||||
TITLE_AS_NUMBERS("html.label.asNumbers", "as Numbers"),
|
TITLE_AS_NUMBERS("html.label.asNumbers", "as Numbers"),
|
||||||
LABEL_AVG_TPS("html.label.averageTps", "Average TPS"),
|
LABEL_AVG_TPS("html.label.averageTps", "Average TPS"),
|
||||||
LABEL_AVG_PLAYERS("html.label.averagePlayers", "Average Players"),
|
LABEL_AVG_PLAYERS("html.label.averagePlayers", "Average Players"),
|
||||||
|
LABEL_CPU("html.label.cpuUsage", "CPU Usage"),
|
||||||
LABEL_AVG_CPU("html.label.averageCpuUsage", "Average CPU Usage"),
|
LABEL_AVG_CPU("html.label.averageCpuUsage", "Average CPU Usage"),
|
||||||
|
LABEL_RAM("html.label.ramUsage", "RAM Usage"),
|
||||||
LABEL_AVG_RAM("html.label.averageRamUsage", "Average RAM Usage"),
|
LABEL_AVG_RAM("html.label.averageRamUsage", "Average RAM Usage"),
|
||||||
LABEL_AVG_ENTITIES("html.label.averageEntities", "Average Entities"),
|
LABEL_AVG_ENTITIES("html.label.averageEntities", "Average Entities"),
|
||||||
LABEL_AVG_CHUNKS("html.label.averageChunks", "Average Chunks"),
|
LABEL_AVG_CHUNKS("html.label.averageChunks", "Average Chunks"),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {useParams} from "react-router-dom";
|
import {useParams} from "react-router-dom";
|
||||||
import {useDataRequest} from "../../../../hooks/dataFetchHook";
|
import {useDataRequest} from "../../../../hooks/dataFetchHook";
|
||||||
import {fetchOptimizedPerformance} from "../../../../service/serverService";
|
import {fetchOptimizedPerformance, fetchPingGraph} from "../../../../service/serverService";
|
||||||
import {ErrorViewBody} from "../../../../views/ErrorView";
|
import {ErrorViewBody} from "../../../../views/ErrorView";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {Card} from "react-bootstrap-v5";
|
import {Card} from "react-bootstrap-v5";
|
||||||
@ -8,13 +8,54 @@ import CardTabs from "../../../CardTabs";
|
|||||||
import {faGears, faHdd, faMap, faMicrochip, faSignal, faTachometerAlt} from "@fortawesome/free-solid-svg-icons";
|
import {faGears, faHdd, faMap, faMicrochip, faSignal, faTachometerAlt} from "@fortawesome/free-solid-svg-icons";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import {ChartLoader} from "../../../navigation/Loader";
|
import {ChartLoader} from "../../../navigation/Loader";
|
||||||
|
import AllPerformanceGraph from "../../../graphs/performance/AllPerformanceGraph";
|
||||||
|
import TpsPerformanceGraph from "../../../graphs/performance/TpsPerformanceGraph";
|
||||||
|
import CpuRamPerformanceGraph from "../../../graphs/performance/CpuRamPerformanceGraph";
|
||||||
|
import WorldPerformanceGraph from "../../../graphs/performance/WorldPerformanceGraph";
|
||||||
|
import DiskPerformanceGraph from "../../../graphs/performance/DiskPerformanceGraph";
|
||||||
|
import PingGraph from "../../../graphs/performance/PingGraph";
|
||||||
|
|
||||||
const PunchCardTab = ({data, loadingError}) => {
|
const AllGraphTab = ({data, dataSeries, loadingError}) => {
|
||||||
|
|
||||||
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||||
if (!data) return <ChartLoader/>;
|
if (!dataSeries) return <ChartLoader style={{height: "450px"}}/>;
|
||||||
|
|
||||||
return <ChartLoader/>
|
return <AllPerformanceGraph id="server-performance-all-chart" data={data} dataSeries={dataSeries}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
const TpsGraphTab = ({data, dataSeries, loadingError}) => {
|
||||||
|
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||||
|
if (!dataSeries) return <ChartLoader style={{height: "450px"}}/>;
|
||||||
|
|
||||||
|
return <TpsPerformanceGraph id="server-performance-tps-chart" data={data} dataSeries={dataSeries}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
const CpuRamGraphTab = ({data, dataSeries, loadingError}) => {
|
||||||
|
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||||
|
if (!dataSeries) return <ChartLoader style={{height: "450px"}}/>;
|
||||||
|
|
||||||
|
return <CpuRamPerformanceGraph id="server-performance-cpuram-chart" data={data} dataSeries={dataSeries}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
const WorldGraphTab = ({data, dataSeries, loadingError}) => {
|
||||||
|
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||||
|
if (!dataSeries) return <ChartLoader style={{height: "450px"}}/>;
|
||||||
|
|
||||||
|
return <WorldPerformanceGraph id="server-performance-world-chart" data={data} dataSeries={dataSeries}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
const DiskGraphTab = ({data, dataSeries, loadingError}) => {
|
||||||
|
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||||
|
if (!dataSeries) return <ChartLoader style={{height: "450px"}}/>;
|
||||||
|
|
||||||
|
return <DiskPerformanceGraph id="server-performance-disk-chart" data={data} dataSeries={dataSeries}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
const PingGraphTab = ({identifier}) => {
|
||||||
|
const {data, loadingError} = useDataRequest(fetchPingGraph, [identifier]);
|
||||||
|
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||||
|
if (!data) return <ChartLoader style={{height: "450px"}}/>;
|
||||||
|
|
||||||
|
return <PingGraph id="server-performance-ping-chart" data={data}/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapToDataSeries(performanceData) {
|
function mapToDataSeries(performanceData) {
|
||||||
@ -63,30 +104,30 @@ const PerformanceGraphsCard = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
mapToDataSeries(data).then(parsed => setParsedData(parsed))
|
mapToDataSeries(data.values).then(parsed => setParsedData(parsed))
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data, setParsedData]);
|
||||||
|
|
||||||
return <Card>
|
return <Card>
|
||||||
<CardTabs tabs={[
|
<CardTabs tabs={[
|
||||||
{
|
{
|
||||||
name: t('html.label.all'), icon: faGears, color: 'blue-grey', href: 'all',
|
name: t('html.label.all'), icon: faGears, color: 'blue-grey', href: 'all',
|
||||||
element: <PunchCardTab data={parsedData} loadingError={loadingError}/>
|
element: <AllGraphTab data={data} dataSeries={parsedData} loadingError={loadingError}/>
|
||||||
}, {
|
}, {
|
||||||
name: t('html.label.tps'), icon: faTachometerAlt, color: 'red', href: 'tps',
|
name: t('html.label.tps'), icon: faTachometerAlt, color: 'red', href: 'tps',
|
||||||
element: <PunchCardTab data={parsedData} loadingError={loadingError}/>
|
element: <TpsGraphTab data={data} dataSeries={parsedData} loadingError={loadingError}/>
|
||||||
}, {
|
}, {
|
||||||
name: t('html.label.cpuRam'), icon: faMicrochip, color: 'light-green', href: 'cpu-ram',
|
name: t('html.label.cpuRam'), icon: faMicrochip, color: 'light-green', href: 'cpu-ram',
|
||||||
element: <PunchCardTab data={parsedData} loadingError={loadingError}/>
|
element: <CpuRamGraphTab data={data} dataSeries={parsedData} loadingError={loadingError}/>
|
||||||
}, {
|
}, {
|
||||||
name: t('html.label.world'), icon: faMap, color: 'purple', href: 'world-load',
|
name: t('html.label.world'), icon: faMap, color: 'purple', href: 'world-load',
|
||||||
element: <PunchCardTab data={parsedData} loadingError={loadingError}/>
|
element: <WorldGraphTab data={data} dataSeries={parsedData} loadingError={loadingError}/>
|
||||||
}, {
|
}, {
|
||||||
name: t('html.label.ping'), icon: faSignal, color: 'amber', href: 'ping',
|
name: t('html.label.ping'), icon: faSignal, color: 'amber', href: 'ping',
|
||||||
element: <PunchCardTab data={parsedData} loadingError={loadingError}/>
|
element: <PingGraphTab identifier={identifier}/>
|
||||||
}, {
|
}, {
|
||||||
name: t('html.label.diskSpace'), icon: faHdd, color: 'green', href: 'disk',
|
name: t('html.label.diskSpace'), icon: faHdd, color: 'green', href: 'disk',
|
||||||
element: <PunchCardTab data={parsedData} loadingError={loadingError}/>
|
element: <DiskGraphTab data={data} dataSeries={parsedData} loadingError={loadingError}/>
|
||||||
},
|
},
|
||||||
]}/>
|
]}/>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -3,7 +3,7 @@ import {useTranslation} from "react-i18next";
|
|||||||
import {tooltip} from "../../util/graphs";
|
import {tooltip} from "../../util/graphs";
|
||||||
import LineGraph from "./LineGraph";
|
import LineGraph from "./LineGraph";
|
||||||
|
|
||||||
const PingGraph = ({data}) => {
|
const PlayerPingGraph = ({data}) => {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const [series, setSeries] = useState([]);
|
const [series, setSeries] = useState([]);
|
||||||
|
|
||||||
@ -37,4 +37,4 @@ const PingGraph = ({data}) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PingGraph
|
export default PlayerPingGraph
|
@ -0,0 +1,182 @@
|
|||||||
|
import React, {useCallback, useEffect} from 'react';
|
||||||
|
|
||||||
|
import {linegraphButtons, tooltip} from "../../../util/graphs";
|
||||||
|
import Highcharts from "highcharts/highstock";
|
||||||
|
import NoDataDisplay from "highcharts/modules/no-data-to-display"
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {useTheme} from "../../../hooks/themeHook";
|
||||||
|
import {withReducedSaturation} from "../../../util/colors";
|
||||||
|
|
||||||
|
const yAxis = [
|
||||||
|
{
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' P';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0,
|
||||||
|
softMax: 2
|
||||||
|
}, {
|
||||||
|
opposite: true,
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' TPS';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0,
|
||||||
|
softMax: 20
|
||||||
|
}, {
|
||||||
|
opposite: true,
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + '%';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0,
|
||||||
|
softMax: 100
|
||||||
|
}, {
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' MB';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0
|
||||||
|
}, {
|
||||||
|
opposite: true,
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' E';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0,
|
||||||
|
softMax: 2
|
||||||
|
}, {
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' C';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const AllPerformanceGraph = ({id, data, dataSeries}) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const {graphTheming, nightModeEnabled} = useTheme();
|
||||||
|
|
||||||
|
const onResize = useCallback(() => {
|
||||||
|
let chartElement = document.getElementById(id);
|
||||||
|
let chartId = chartElement?.getAttribute('data-highcharts-chart');
|
||||||
|
const chart = chartId !== undefined ? Highcharts.charts[chartId] : undefined;
|
||||||
|
|
||||||
|
if (chart && chart.yAxis && chart.yAxis.length) {
|
||||||
|
const newWidth = window.innerWidth
|
||||||
|
chart.yAxis[0].update({labels: {enabled: newWidth >= 900}});
|
||||||
|
chart.yAxis[1].update({labels: {enabled: newWidth >= 900}});
|
||||||
|
chart.yAxis[2].update({labels: {enabled: newWidth >= 1000}});
|
||||||
|
chart.yAxis[3].update({labels: {enabled: newWidth >= 1000}});
|
||||||
|
chart.yAxis[4].update({labels: {enabled: newWidth >= 1400}});
|
||||||
|
chart.yAxis[5].update({labels: {enabled: newWidth >= 1400}});
|
||||||
|
}
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("resize", onResize);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", onResize);
|
||||||
|
}
|
||||||
|
}, [onResize])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const zones = {
|
||||||
|
tps: [{
|
||||||
|
value: data.zones.tpsThresholdMed,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.low) : data.colors.low
|
||||||
|
}, {
|
||||||
|
value: data.zones.tpsThresholdHigh,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.med) : data.colors.med
|
||||||
|
}, {
|
||||||
|
value: 30,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.high) : data.colors.high
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
const spline = 'spline'
|
||||||
|
|
||||||
|
const series = {
|
||||||
|
playersOnline: {
|
||||||
|
name: t('html.label.playersOnline'),
|
||||||
|
type: 'areaspline',
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: dataSeries.playersOnline,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.playersOnline) : data.colors.playersOnline,
|
||||||
|
yAxis: 0
|
||||||
|
}, tps: {
|
||||||
|
name: t('html.label.tps'),
|
||||||
|
type: spline,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.high) : data.colors.high,
|
||||||
|
zones: zones.tps,
|
||||||
|
tooltip: tooltip.twoDecimals,
|
||||||
|
data: dataSeries.tps,
|
||||||
|
yAxis: 1
|
||||||
|
}, cpu: {
|
||||||
|
name: t('html.label.cpu'),
|
||||||
|
type: spline,
|
||||||
|
tooltip: tooltip.twoDecimals,
|
||||||
|
data: dataSeries.cpu,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.cpu) : data.colors.cpu,
|
||||||
|
yAxis: 2
|
||||||
|
}, ram: {
|
||||||
|
name: t('html.label.ram'),
|
||||||
|
type: spline,
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: dataSeries.ram,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.ram) : data.colors.ram,
|
||||||
|
yAxis: 3
|
||||||
|
}, entities: {
|
||||||
|
name: t('html.label.loadedEntities'),
|
||||||
|
type: spline,
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: dataSeries.entities,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.entities) : data.colors.entities,
|
||||||
|
yAxis: 4
|
||||||
|
}, chunks: {
|
||||||
|
name: t('html.label.loadedChunks'),
|
||||||
|
type: spline,
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: dataSeries.chunks,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.chunks) : data.colors.chunks,
|
||||||
|
yAxis: 5
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NoDataDisplay(Highcharts);
|
||||||
|
Highcharts.setOptions({lang: {noData: t('html.label.noDataToDisplay')}})
|
||||||
|
Highcharts.setOptions(graphTheming);
|
||||||
|
Highcharts.stockChart(id, {
|
||||||
|
rangeSelector: {
|
||||||
|
selected: 2,
|
||||||
|
buttons: linegraphButtons
|
||||||
|
},
|
||||||
|
yAxis,
|
||||||
|
title: {text: ''},
|
||||||
|
plotOptions: {
|
||||||
|
areaspline: {
|
||||||
|
fillOpacity: 0.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
series: [series.playersOnline, series.tps, series.cpu, series.ram, series.entities, series.chunks]
|
||||||
|
});
|
||||||
|
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||||
|
<span className="loader"/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AllPerformanceGraph
|
@ -0,0 +1,94 @@
|
|||||||
|
import React, {useEffect} from 'react';
|
||||||
|
|
||||||
|
import {linegraphButtons, tooltip} from "../../../util/graphs";
|
||||||
|
import Highcharts from "highcharts/highstock";
|
||||||
|
import NoDataDisplay from "highcharts/modules/no-data-to-display"
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {useTheme} from "../../../hooks/themeHook";
|
||||||
|
import {withReducedSaturation} from "../../../util/colors";
|
||||||
|
|
||||||
|
const CpuRamPerformanceGraph = ({id, data, dataSeries}) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const {graphTheming, nightModeEnabled} = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const spline = 'spline'
|
||||||
|
|
||||||
|
const series = {
|
||||||
|
playersOnline: {
|
||||||
|
name: t('html.label.playersOnline'),
|
||||||
|
type: 'areaspline',
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: dataSeries.playersOnline,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.playersOnline) : data.colors.playersOnline,
|
||||||
|
yAxis: 0
|
||||||
|
}, cpu: {
|
||||||
|
name: t('html.label.cpu'),
|
||||||
|
type: spline,
|
||||||
|
tooltip: tooltip.twoDecimals,
|
||||||
|
data: dataSeries.cpu,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.cpu) : data.colors.cpu,
|
||||||
|
yAxis: 1
|
||||||
|
}, ram: {
|
||||||
|
name: t('html.label.ram'),
|
||||||
|
type: spline,
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: dataSeries.ram,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.ram) : data.colors.ram,
|
||||||
|
yAxis: 2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NoDataDisplay(Highcharts);
|
||||||
|
Highcharts.setOptions({lang: {noData: t('html.label.noDataToDisplay')}})
|
||||||
|
Highcharts.setOptions(graphTheming);
|
||||||
|
Highcharts.stockChart(id, {
|
||||||
|
rangeSelector: {
|
||||||
|
selected: 1, // TODO Sync range selectors state
|
||||||
|
buttons: linegraphButtons
|
||||||
|
},
|
||||||
|
yAxis: [{
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' ' + t('html.unit.players')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0,
|
||||||
|
softMax: 2
|
||||||
|
}, {
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' %'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0,
|
||||||
|
softMax: 100
|
||||||
|
}, {
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' MB'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0
|
||||||
|
}],
|
||||||
|
title: {text: ''},
|
||||||
|
plotOptions: {
|
||||||
|
areaspline: {
|
||||||
|
fillOpacity: 0.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
series: [series.playersOnline, series.cpu, series.ram]
|
||||||
|
});
|
||||||
|
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||||
|
<span className="loader"/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CpuRamPerformanceGraph
|
@ -0,0 +1,75 @@
|
|||||||
|
import React, {useEffect} from 'react';
|
||||||
|
|
||||||
|
import {linegraphButtons, tooltip} from "../../../util/graphs";
|
||||||
|
import Highcharts from "highcharts/highstock";
|
||||||
|
import NoDataDisplay from "highcharts/modules/no-data-to-display"
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {useTheme} from "../../../hooks/themeHook";
|
||||||
|
import {withReducedSaturation} from "../../../util/colors";
|
||||||
|
|
||||||
|
const DiskPerformanceGraph = ({id, data, dataSeries}) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const {graphTheming, nightModeEnabled} = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const zones = {
|
||||||
|
disk: [{
|
||||||
|
value: data.zones.diskThresholdMed,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.low) : data.colors.low
|
||||||
|
}, {
|
||||||
|
value: data.zones.diskThresholdHigh,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.med) : data.colors.med
|
||||||
|
}, {
|
||||||
|
value: Number.MAX_VALUE,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.high) : data.colors.high
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
const series = {
|
||||||
|
disk: {
|
||||||
|
name: t('html.label.disk'),
|
||||||
|
type: 'areaspline',
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.high) : data.colors.high,
|
||||||
|
zones: zones.disk,
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: dataSeries.disk
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NoDataDisplay(Highcharts);
|
||||||
|
Highcharts.setOptions({lang: {noData: t('html.label.noDataToDisplay')}})
|
||||||
|
Highcharts.setOptions(graphTheming);
|
||||||
|
Highcharts.stockChart(id, {
|
||||||
|
rangeSelector: {
|
||||||
|
selected: 2,
|
||||||
|
buttons: linegraphButtons
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' MB';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0
|
||||||
|
},
|
||||||
|
title: {text: ''},
|
||||||
|
plotOptions: {
|
||||||
|
areaspline: {
|
||||||
|
fillOpacity: 0.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
series: [series.disk]
|
||||||
|
});
|
||||||
|
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||||
|
<span className="loader"/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DiskPerformanceGraph
|
@ -0,0 +1,72 @@
|
|||||||
|
import React, {useEffect} from 'react';
|
||||||
|
|
||||||
|
import {linegraphButtons, tooltip} from "../../../util/graphs";
|
||||||
|
import Highcharts from "highcharts/highstock";
|
||||||
|
import NoDataDisplay from "highcharts/modules/no-data-to-display"
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {useTheme} from "../../../hooks/themeHook";
|
||||||
|
import {withReducedSaturation} from "../../../util/colors";
|
||||||
|
|
||||||
|
const PingGraph = ({id, data}) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const {graphTheming, nightModeEnabled} = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const spline = 'spline'
|
||||||
|
|
||||||
|
const series = {
|
||||||
|
avgPing: {
|
||||||
|
name: t('html.label.averagePing'),
|
||||||
|
type: spline,
|
||||||
|
tooltip: tooltip.twoDecimals,
|
||||||
|
data: data.avg_ping_series,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.avg) : data.colors.avg,
|
||||||
|
},
|
||||||
|
maxPing: {
|
||||||
|
name: t('html.label.worstPing'),
|
||||||
|
type: spline,
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: data.max_ping_series,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.max) : data.colors.max,
|
||||||
|
},
|
||||||
|
minPing: {
|
||||||
|
name: t('html.label.bestPing'),
|
||||||
|
type: spline,
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: data.min_ping_series,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.min) : data.colors.min,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NoDataDisplay(Highcharts);
|
||||||
|
Highcharts.setOptions({lang: {noData: t('html.label.noDataToDisplay')}})
|
||||||
|
Highcharts.setOptions(graphTheming);
|
||||||
|
Highcharts.stockChart(id, {
|
||||||
|
rangeSelector: {
|
||||||
|
selected: 2,
|
||||||
|
buttons: linegraphButtons
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' ms'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0
|
||||||
|
},
|
||||||
|
title: {text: ''},
|
||||||
|
legend: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
series: [series.avgPing, series.maxPing, series.minPing]
|
||||||
|
});
|
||||||
|
}, [data, graphTheming, nightModeEnabled, id, t])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||||
|
<span className="loader"/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PingGraph
|
@ -0,0 +1,91 @@
|
|||||||
|
import React, {useEffect} from 'react';
|
||||||
|
|
||||||
|
import {linegraphButtons, tooltip} from "../../../util/graphs";
|
||||||
|
import Highcharts from "highcharts/highstock";
|
||||||
|
import NoDataDisplay from "highcharts/modules/no-data-to-display"
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {useTheme} from "../../../hooks/themeHook";
|
||||||
|
import {withReducedSaturation} from "../../../util/colors";
|
||||||
|
|
||||||
|
const TpsPerformanceGraph = ({id, data, dataSeries}) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const {graphTheming, nightModeEnabled} = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const zones = {
|
||||||
|
tps: [{
|
||||||
|
value: data.zones.tpsThresholdMed,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.low) : data.colors.low
|
||||||
|
}, {
|
||||||
|
value: data.zones.tpsThresholdHigh,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.med) : data.colors.med
|
||||||
|
}, {
|
||||||
|
value: 30,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.high) : data.colors.high
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
const spline = 'spline'
|
||||||
|
const series = {
|
||||||
|
playersOnline: {
|
||||||
|
name: t('html.label.playersOnline'),
|
||||||
|
type: 'areaspline',
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: dataSeries.playersOnline,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.playersOnline) : data.colors.playersOnline,
|
||||||
|
yAxis: 0
|
||||||
|
}, tps: {
|
||||||
|
name: t('html.label.tps'),
|
||||||
|
type: spline,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.high) : data.colors.high,
|
||||||
|
zones: zones.tps,
|
||||||
|
tooltip: tooltip.twoDecimals,
|
||||||
|
data: dataSeries.tps,
|
||||||
|
yAxis: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NoDataDisplay(Highcharts);
|
||||||
|
Highcharts.setOptions({lang: {noData: t('html.label.noDataToDisplay')}})
|
||||||
|
Highcharts.setOptions(graphTheming);
|
||||||
|
Highcharts.stockChart(id, {
|
||||||
|
rangeSelector: {
|
||||||
|
selected: 1,
|
||||||
|
buttons: linegraphButtons
|
||||||
|
},
|
||||||
|
yAxis: [{
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' ' + t('html.unit.players')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0,
|
||||||
|
softMax: 2
|
||||||
|
}, {
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' ' + t('html.label.tps')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
title: {text: ''},
|
||||||
|
plotOptions: {
|
||||||
|
areaspline: {
|
||||||
|
fillOpacity: 0.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
series: [series.playersOnline, series.tps]
|
||||||
|
});
|
||||||
|
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||||
|
<span className="loader"/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TpsPerformanceGraph
|
@ -0,0 +1,93 @@
|
|||||||
|
import React, {useEffect} from 'react';
|
||||||
|
|
||||||
|
import {linegraphButtons, tooltip} from "../../../util/graphs";
|
||||||
|
import Highcharts from "highcharts/highstock";
|
||||||
|
import NoDataDisplay from "highcharts/modules/no-data-to-display"
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {useTheme} from "../../../hooks/themeHook";
|
||||||
|
import {withReducedSaturation} from "../../../util/colors";
|
||||||
|
|
||||||
|
const WorldPerformanceGraph = ({id, data, dataSeries}) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const {graphTheming, nightModeEnabled} = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const spline = 'spline'
|
||||||
|
|
||||||
|
const series = {
|
||||||
|
playersOnline: {
|
||||||
|
name: t('html.label.playersOnline'),
|
||||||
|
type: 'areaspline',
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: dataSeries.playersOnline,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.playersOnline) : data.colors.playersOnline,
|
||||||
|
yAxis: 0
|
||||||
|
}, entities: {
|
||||||
|
name: t('html.label.loadedEntities'),
|
||||||
|
type: spline,
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: dataSeries.entities,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.entities) : data.colors.entities,
|
||||||
|
yAxis: 1
|
||||||
|
}, chunks: {
|
||||||
|
name: t('html.label.loadedChunks'),
|
||||||
|
type: spline,
|
||||||
|
tooltip: tooltip.zeroDecimals,
|
||||||
|
data: dataSeries.chunks,
|
||||||
|
color: nightModeEnabled ? withReducedSaturation(data.colors.chunks) : data.colors.chunks,
|
||||||
|
yAxis: 2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NoDataDisplay(Highcharts);
|
||||||
|
Highcharts.setOptions({lang: {noData: t('html.label.noDataToDisplay')}})
|
||||||
|
Highcharts.setOptions(graphTheming);
|
||||||
|
Highcharts.stockChart(id, {
|
||||||
|
rangeSelector: {
|
||||||
|
selected: 2,
|
||||||
|
buttons: linegraphButtons
|
||||||
|
},
|
||||||
|
yAxis: [{
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' ' + t('html.unit.players')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0,
|
||||||
|
softMax: 2
|
||||||
|
}, {
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' ' + t('html.label.entities')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0,
|
||||||
|
}, {
|
||||||
|
labels: {
|
||||||
|
formatter: function () {
|
||||||
|
return this.value + ' ' + t('html.unit.chunks')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softMin: 0,
|
||||||
|
}],
|
||||||
|
title: {text: ''},
|
||||||
|
plotOptions: {
|
||||||
|
areaspline: {
|
||||||
|
fillOpacity: 0.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
series: [series.playersOnline, series.entities, series.chunks]
|
||||||
|
});
|
||||||
|
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||||
|
<span className="loader"/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WorldPerformanceGraph
|
@ -14,8 +14,8 @@ export const CardLoader = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChartLoader = () => {
|
export const ChartLoader = ({style}) => {
|
||||||
return <div className="chart-area loading">
|
return <div className="chart-area loading" style={style}>
|
||||||
<Loader/>
|
<Loader/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,10 @@ export const NavigationContextProvider = ({children}) => {
|
|||||||
|
|
||||||
const finishUpdate = useCallback((date, formatted) => {
|
const finishUpdate = useCallback((date, formatted) => {
|
||||||
// TODO Logic to retry if received data is too old
|
// TODO Logic to retry if received data is too old
|
||||||
|
if (date) {
|
||||||
setLastUpdate({date, formatted});
|
setLastUpdate({date, formatted});
|
||||||
setUpdating(false);
|
setUpdating(false);
|
||||||
|
}
|
||||||
}, [setLastUpdate, setUpdating]);
|
}, [setLastUpdate, setUpdating]);
|
||||||
|
|
||||||
const toggleSidebar = useCallback(() => {
|
const toggleSidebar = useCallback(() => {
|
||||||
|
@ -119,3 +119,9 @@ export const fetchOptimizedPerformance = async (identifier) => {
|
|||||||
const url = `/v1/graph?type=optimizedPerformance&server=${identifier}×tamp=${timestamp}`;
|
const url = `/v1/graph?type=optimizedPerformance&server=${identifier}×tamp=${timestamp}`;
|
||||||
return doGetRequest(url);
|
return doGetRequest(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const fetchPingGraph = async (identifier) => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const url = `/v1/graph?type=aggregatedPing&server=${identifier}×tamp=${timestamp}`;
|
||||||
|
return doGetRequest(url);
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ import ServerPie from "../../components/graphs/ServerPie";
|
|||||||
import ServerAccordion from "../../components/accordion/ServerAccordion";
|
import ServerAccordion from "../../components/accordion/ServerAccordion";
|
||||||
import {usePlayer} from "../layout/PlayerPage";
|
import {usePlayer} from "../layout/PlayerPage";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import PingGraph from "../../components/graphs/PingGraph";
|
import PlayerPingGraph from "../../components/graphs/PlayerPingGraph";
|
||||||
import LoadIn from "../../components/animation/LoadIn";
|
import LoadIn from "../../components/animation/LoadIn";
|
||||||
|
|
||||||
const PingGraphCard = ({player}) => {
|
const PingGraphCard = ({player}) => {
|
||||||
@ -23,7 +23,7 @@ const PingGraphCard = ({player}) => {
|
|||||||
<Fa icon={faSignal} className="col-amber"/> {t('html.label.ping')}
|
<Fa icon={faSignal} className="col-amber"/> {t('html.label.ping')}
|
||||||
</h6>
|
</h6>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
{hasPingData && <PingGraph data={player.ping_graph}/>}
|
{hasPingData && <PlayerPingGraph data={player.ping_graph}/>}
|
||||||
{!hasPingData && <Card.Body><p>{t('generic.noData')}</p></Card.Body>}
|
{!hasPingData && <Card.Body><p>{t('generic.noData')}</p></Card.Body>}
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user