mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-12-23 09:37:54 +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"),
|
||||
LABEL_AVG_TPS("html.label.averageTps", "Average TPS"),
|
||||
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_RAM("html.label.ramUsage", "RAM Usage"),
|
||||
LABEL_AVG_RAM("html.label.averageRamUsage", "Average RAM Usage"),
|
||||
LABEL_AVG_ENTITIES("html.label.averageEntities", "Average Entities"),
|
||||
LABEL_AVG_CHUNKS("html.label.averageChunks", "Average Chunks"),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {useParams} from "react-router-dom";
|
||||
import {useDataRequest} from "../../../../hooks/dataFetchHook";
|
||||
import {fetchOptimizedPerformance} from "../../../../service/serverService";
|
||||
import {fetchOptimizedPerformance, fetchPingGraph} from "../../../../service/serverService";
|
||||
import {ErrorViewBody} from "../../../../views/ErrorView";
|
||||
import {useTranslation} from "react-i18next";
|
||||
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 React, {useEffect, useState} from "react";
|
||||
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 (!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) {
|
||||
@ -63,30 +104,30 @@ const PerformanceGraphsCard = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
mapToDataSeries(data).then(parsed => setParsedData(parsed))
|
||||
mapToDataSeries(data.values).then(parsed => setParsedData(parsed))
|
||||
}
|
||||
}, [data]);
|
||||
}, [data, setParsedData]);
|
||||
|
||||
return <Card>
|
||||
<CardTabs tabs={[
|
||||
{
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
element: <PunchCardTab data={parsedData} loadingError={loadingError}/>
|
||||
element: <PingGraphTab identifier={identifier}/>
|
||||
}, {
|
||||
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>
|
||||
|
@ -3,7 +3,7 @@ import {useTranslation} from "react-i18next";
|
||||
import {tooltip} from "../../util/graphs";
|
||||
import LineGraph from "./LineGraph";
|
||||
|
||||
const PingGraph = ({data}) => {
|
||||
const PlayerPingGraph = ({data}) => {
|
||||
const {t} = useTranslation();
|
||||
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 = () => {
|
||||
return <div className="chart-area loading">
|
||||
export const ChartLoader = ({style}) => {
|
||||
return <div className="chart-area loading" style={style}>
|
||||
<Loader/>
|
||||
</div>
|
||||
}
|
||||
|
@ -19,8 +19,10 @@ export const NavigationContextProvider = ({children}) => {
|
||||
|
||||
const finishUpdate = useCallback((date, formatted) => {
|
||||
// TODO Logic to retry if received data is too old
|
||||
setLastUpdate({date, formatted});
|
||||
setUpdating(false);
|
||||
if (date) {
|
||||
setLastUpdate({date, formatted});
|
||||
setUpdating(false);
|
||||
}
|
||||
}, [setLastUpdate, setUpdating]);
|
||||
|
||||
const toggleSidebar = useCallback(() => {
|
||||
|
@ -119,3 +119,9 @@ export const fetchOptimizedPerformance = async (identifier) => {
|
||||
const url = `/v1/graph?type=optimizedPerformance&server=${identifier}×tamp=${timestamp}`;
|
||||
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 {usePlayer} from "../layout/PlayerPage";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import PingGraph from "../../components/graphs/PingGraph";
|
||||
import PlayerPingGraph from "../../components/graphs/PlayerPingGraph";
|
||||
import LoadIn from "../../components/animation/LoadIn";
|
||||
|
||||
const PingGraphCard = ({player}) => {
|
||||
@ -23,7 +23,7 @@ const PingGraphCard = ({player}) => {
|
||||
<Fa icon={faSignal} className="col-amber"/> {t('html.label.ping')}
|
||||
</h6>
|
||||
</Card.Header>
|
||||
{hasPingData && <PingGraph data={player.ping_graph}/>}
|
||||
{hasPingData && <PlayerPingGraph data={player.ping_graph}/>}
|
||||
{!hasPingData && <Card.Body><p>{t('generic.noData')}</p></Card.Body>}
|
||||
</Card>
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user