mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-12-27 09:00:28 +08:00
Implemented Network performance tab in React
Affects issues: - Implemented #2469
This commit is contained in:
parent
3d64e36159
commit
8cdbebf191
@ -179,6 +179,9 @@ public class NetworkPerformanceJSONResolver implements Resolver {
|
||||
numbers.put("avg_server_downtime_24h", "-");
|
||||
}
|
||||
|
||||
numbers.put("players_30d", format(tpsDataMonth.averagePlayers()));
|
||||
numbers.put("players_7d", format(tpsDataWeek.averagePlayers()));
|
||||
numbers.put("players_24h", format(tpsDataDay.averagePlayers()));
|
||||
numbers.put("tps_30d", format(tpsDataMonth.averageTPS()));
|
||||
numbers.put("tps_7d", format(tpsDataWeek.averageTPS()));
|
||||
numbers.put("tps_24h", format(tpsDataDay.averageTPS()));
|
||||
|
@ -180,6 +180,8 @@ public enum HtmlLang implements Lang {
|
||||
LABEL_AVG("html.label.average", "Average"),
|
||||
TITLE_PERFORMANCE_AS_NUMBERS("html.label.performanceAsNumbers", "Performance as Numbers"),
|
||||
LABEL_SERVER_DOWNTIME("html.label.serverDowntime", "Server Downtime"),
|
||||
LABEL_TOTAL_SERVER_DOWNTIME("html.label.totalServerDowntime", "Total Server Downtime"),
|
||||
LABEL_AVERAGE_SERVER_DOWNTIME("html.label.averageServerDowntime", "Average Downtime / Server"),
|
||||
LABEL_DURING_LOW_TPS("html.label.duringLowTps", "During Low TPS Spikes:"),
|
||||
TEXT_NO_LOW_TPS("html.text.noLowTps", "No low tps spikes"),
|
||||
// Player Page
|
||||
@ -257,6 +259,8 @@ public enum HtmlLang implements Lang {
|
||||
LABEL_PROJECTION_MERCATOR("html.label.geoProjection.mercator", "Mercator"),
|
||||
LABEL_PROJECTION_EQUAL_EARTH("html.label.geoProjection.equalEarth", "Equal Earth"),
|
||||
LABEL_PROJECTION_ORTOGRAPHIC("html.label.geoProjection.ortographic", "Ortographic"),
|
||||
LABEL_SERVER_SELECTOR("html.label.serverSelector", "Server selector"),
|
||||
LABEL_APPLY("html.label.apply", "Apply"),
|
||||
|
||||
LOGIN_LOGIN("html.login.login", "Login"),
|
||||
LOGIN_LOGOUT("html.login.logout", "Logout"),
|
||||
|
@ -41,6 +41,7 @@ const NetworkSessions = React.lazy(() => import("./views/network/NetworkSessions
|
||||
const NetworkJoinAddresses = React.lazy(() => import("./views/network/NetworkJoinAddresses"));
|
||||
const NetworkGeolocations = React.lazy(() => import("./views/network/NetworkGeolocations"));
|
||||
const NetworkPlayerbaseOverview = React.lazy(() => import("./views/network/NetworkPlayerbaseOverview"));
|
||||
const NetworkPerformance = React.lazy(() => import("./views/network/NetworkPerformance"));
|
||||
|
||||
const PlayersPage = React.lazy(() => import("./views/layout/PlayersPage"));
|
||||
const AllPlayers = React.lazy(() => import("./views/players/AllPlayers"));
|
||||
@ -124,6 +125,7 @@ function App() {
|
||||
<Route path="overview" element={<Lazy><NetworkOverview/></Lazy>}/>
|
||||
<Route path="serversOverview" element={<Lazy><NetworkServers/></Lazy>}/>
|
||||
<Route path="sessions" element={<Lazy><NetworkSessions/></Lazy>}/>
|
||||
<Route path="performance" element={<Lazy><NetworkPerformance/></Lazy>}/>
|
||||
<Route path="playerbase" element={<Lazy><NetworkPlayerbaseOverview/></Lazy>}/>
|
||||
<Route path="join-addresses" element={<Lazy><NetworkJoinAddresses/></Lazy>}/>
|
||||
<Route path="players" element={<Lazy><AllPlayers/></Lazy>}/>
|
||||
|
@ -0,0 +1,157 @@
|
||||
import React from 'react';
|
||||
import CardTabs from "../../CardTabs";
|
||||
import {
|
||||
faDragon,
|
||||
faHdd,
|
||||
faMap,
|
||||
faMicrochip,
|
||||
faSignal,
|
||||
faTachometerAlt,
|
||||
faUser
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import {Card} from "react-bootstrap-v5";
|
||||
import {useDataRequest} from "../../../hooks/dataFetchHook";
|
||||
import {fetchPingGraph} from "../../../service/serverService";
|
||||
import {tooltip, yAxisConfigurations} from "../../../util/graphs";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {CardLoader, ChartLoader} from "../../navigation/Loader";
|
||||
import LineGraph from "../../graphs/LineGraph";
|
||||
import {ErrorViewBody, ErrorViewCard} from "../../../views/ErrorView";
|
||||
import PingGraph from "../../graphs/performance/PingGraph";
|
||||
import {useMetadata} from "../../../hooks/metadataHook";
|
||||
|
||||
const Tab = ({data, yAxis}) => {
|
||||
return (
|
||||
<LineGraph id={'performance-' + new Date().getTime()} series={data} legendEnabled tall yAxis={yAxis}/>
|
||||
)
|
||||
}
|
||||
|
||||
const PingTab = ({identifier}) => {
|
||||
const {data, loadingError} = useDataRequest(fetchPingGraph, [identifier]);
|
||||
|
||||
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||
if (!data) return <ChartLoader style={{height: "450px"}}/>;
|
||||
|
||||
return <PingGraph id="network-performance-ping-chart" data={data}/>;
|
||||
}
|
||||
|
||||
const PerformanceGraphsCard = ({data}) => {
|
||||
const {t} = useTranslation();
|
||||
const {networkMetadata} = useMetadata();
|
||||
|
||||
if (!data || !Object.values(data).length) return <CardLoader/>
|
||||
|
||||
const zones = {
|
||||
tps: [{
|
||||
value: data.zones.tpsThresholdMed,
|
||||
color: data.colors.low
|
||||
}, {
|
||||
value: data.zones.tpsThresholdHigh,
|
||||
color: data.colors.med
|
||||
}, {
|
||||
value: 30,
|
||||
color: data.colors.high
|
||||
}],
|
||||
disk: [{
|
||||
value: data.zones.diskThresholdMed,
|
||||
color: data.colors.low
|
||||
}, {
|
||||
value: data.zones.diskThresholdHigh,
|
||||
color: data.colors.med
|
||||
}, {
|
||||
value: Number.MAX_VALUE,
|
||||
color: data.colors.high
|
||||
}]
|
||||
};
|
||||
const serverData = [];
|
||||
for (let i = 0; i < data.servers.length; i++) {
|
||||
const server = data.servers[i];
|
||||
const values = data.values[i];
|
||||
serverData.push({
|
||||
serverName: server.serverName,
|
||||
values
|
||||
});
|
||||
}
|
||||
|
||||
const series = {
|
||||
players: [],
|
||||
tps: [],
|
||||
cpu: [],
|
||||
ram: [],
|
||||
entities: [],
|
||||
chunks: [],
|
||||
disk: []
|
||||
}
|
||||
|
||||
const spline = 'spline';
|
||||
|
||||
for (const server of serverData) {
|
||||
series.players.push({
|
||||
name: server.serverName, type: spline, tooltip: tooltip.zeroDecimals,
|
||||
data: server.values.playersOnline, color: data.colors.playersOnline, yAxis: 0
|
||||
});
|
||||
series.tps.push({
|
||||
name: server.serverName, type: spline, tooltip: tooltip.twoDecimals,
|
||||
data: server.values.tps, color: data.colors.high, zones: zones.tps, yAxis: 0
|
||||
});
|
||||
series.cpu.push({
|
||||
name: server.serverName, type: spline, tooltip: tooltip.twoDecimals,
|
||||
data: server.values.cpu, color: data.colors.cpu, yAxis: 0
|
||||
});
|
||||
series.ram.push({
|
||||
name: server.serverName, type: spline, tooltip: tooltip.zeroDecimals,
|
||||
data: server.values.ram, color: data.colors.ram, yAxis: 0
|
||||
});
|
||||
series.entities.push({
|
||||
name: server.serverName, type: spline, tooltip: tooltip.zeroDecimals,
|
||||
data: server.values.entities, color: data.colors.entities, yAxis: 0
|
||||
});
|
||||
series.chunks.push({
|
||||
name: server.serverName, type: spline, tooltip: tooltip.zeroDecimals,
|
||||
data: server.values.chunks, color: data.colors.chunks, yAxis: 0
|
||||
});
|
||||
series.disk.push({
|
||||
name: server.serverName, type: spline, tooltip: tooltip.zeroDecimals,
|
||||
data: server.values.disk, color: data.colors.high, zones: zones.disk, yAxis: 0
|
||||
});
|
||||
}
|
||||
|
||||
if (data.errors.length) {
|
||||
return <ErrorViewCard error={data.errors[0]}/>
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTabs tabs={[
|
||||
{
|
||||
name: t('html.label.playersOnline'), icon: faUser, color: 'light-blue', href: 'players-online',
|
||||
element: <Tab data={series.players} yAxis={yAxisConfigurations.PLAYERS_ONLINE}/>
|
||||
}, {
|
||||
name: t('html.label.tps'), icon: faTachometerAlt, color: 'red', href: 'tps',
|
||||
element: <Tab data={series.tps} yAxis={yAxisConfigurations.TPS}/>
|
||||
}, {
|
||||
name: t('html.label.cpu'), icon: faTachometerAlt, color: 'amber', href: 'cpu',
|
||||
element: <Tab data={series.cpu} yAxis={yAxisConfigurations.CPU}/>
|
||||
}, {
|
||||
name: t('html.label.ram'), icon: faMicrochip, color: 'light-green', href: 'ram',
|
||||
element: <Tab data={series.ram} yAxis={yAxisConfigurations.RAM_OR_DISK}/>
|
||||
}, {
|
||||
name: t('html.label.entities'), icon: faDragon, color: 'purple', href: 'entities',
|
||||
element: <Tab data={series.entities} yAxis={yAxisConfigurations.ENTITIES}/>
|
||||
}, {
|
||||
name: t('html.label.loadedChunks'), icon: faMap, color: 'blue-grey', href: 'chunks',
|
||||
element: <Tab data={series.chunks} yAxis={yAxisConfigurations.CHUNKS}/>
|
||||
}, {
|
||||
name: t('html.label.diskSpace'), icon: faHdd, color: 'green', href: 'disk',
|
||||
element: <Tab data={series.disk} yAxis={yAxisConfigurations.RAM_OR_DISK}/>
|
||||
}, {
|
||||
name: t('html.label.ping'), icon: faSignal, color: 'amber', href: 'ping',
|
||||
element: networkMetadata ? <PingTab identifier={networkMetadata.currentServer.serverUUID}/> :
|
||||
<ChartLoader/>
|
||||
},
|
||||
]}/>
|
||||
</Card>
|
||||
)
|
||||
};
|
||||
|
||||
export default PerformanceGraphsCard
|
@ -14,6 +14,7 @@ import CpuRamPerformanceGraph from "../../../graphs/performance/CpuRamPerformanc
|
||||
import WorldPerformanceGraph from "../../../graphs/performance/WorldPerformanceGraph";
|
||||
import DiskPerformanceGraph from "../../../graphs/performance/DiskPerformanceGraph";
|
||||
import PingGraph from "../../../graphs/performance/PingGraph";
|
||||
import {mapPerformanceDataToSeries} from "../../../../util/graphs";
|
||||
|
||||
const AllGraphTab = ({data, dataSeries, loadingError}) => {
|
||||
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||
@ -58,43 +59,6 @@ const PingGraphTab = ({identifier}) => {
|
||||
return <PingGraph id="server-performance-ping-chart" data={data}/>;
|
||||
}
|
||||
|
||||
function mapToDataSeries(performanceData) {
|
||||
const playersOnline = [];
|
||||
const tps = [];
|
||||
const cpu = [];
|
||||
const ram = [];
|
||||
const entities = [];
|
||||
const chunks = [];
|
||||
const disk = [];
|
||||
|
||||
return new Promise((resolve => {
|
||||
let i = 0;
|
||||
const length = performanceData.length;
|
||||
|
||||
function processNextThousand() {
|
||||
const to = Math.min(i + 1000, length);
|
||||
for (i; i < to; i++) {
|
||||
const entry = performanceData[i];
|
||||
const date = entry[0];
|
||||
playersOnline[i] = [date, entry[1]];
|
||||
tps[i] = [date, entry[2]];
|
||||
cpu[i] = [date, entry[3]];
|
||||
ram[i] = [date, entry[4]];
|
||||
entities[i] = [date, entry[5]];
|
||||
chunks[i] = [date, entry[6]];
|
||||
disk[i] = [date, entry[7]];
|
||||
}
|
||||
if (i >= length) {
|
||||
resolve({playersOnline, tps, cpu, ram, entities, chunks, disk})
|
||||
} else {
|
||||
setTimeout(processNextThousand, 10);
|
||||
}
|
||||
}
|
||||
|
||||
processNextThousand();
|
||||
}))
|
||||
}
|
||||
|
||||
const PerformanceGraphsCard = () => {
|
||||
const {t} = useTranslation();
|
||||
|
||||
@ -104,7 +68,7 @@ const PerformanceGraphsCard = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
mapToDataSeries(data.values).then(parsed => setParsedData(parsed))
|
||||
mapPerformanceDataToSeries(data.values).then(parsed => setParsedData(parsed))
|
||||
}
|
||||
}, [data, setParsedData]);
|
||||
|
||||
|
@ -6,7 +6,7 @@ import NoDataDisplay from "highcharts/modules/no-data-to-display"
|
||||
import Accessibility from "highcharts/modules/accessibility"
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
const LineGraph = ({id, series}) => {
|
||||
const LineGraph = ({id, series, legendEnabled, tall, yAxis}) => {
|
||||
const {t} = useTranslation()
|
||||
const {graphTheming, nightModeEnabled} = useTheme();
|
||||
|
||||
@ -20,7 +20,7 @@ const LineGraph = ({id, series}) => {
|
||||
selected: 2,
|
||||
buttons: linegraphButtons
|
||||
},
|
||||
yAxis: {
|
||||
yAxis: yAxis || {
|
||||
softMax: 2,
|
||||
softMin: 0
|
||||
},
|
||||
@ -30,12 +30,17 @@ const LineGraph = ({id, series}) => {
|
||||
fillOpacity: nightModeEnabled ? 0.2 : 0.4
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
enabled: legendEnabled
|
||||
},
|
||||
series: series
|
||||
})
|
||||
}, [series, graphTheming, id, t, nightModeEnabled])
|
||||
}, [series, graphTheming, id, t, nightModeEnabled, legendEnabled, yAxis])
|
||||
|
||||
const style = tall ? {height: "450px"} : undefined;
|
||||
|
||||
return (
|
||||
<div className="chart-area" id={id}>
|
||||
<div className="chart-area" style={style} id={id}>
|
||||
<span className="loader"/>
|
||||
</div>
|
||||
)
|
||||
|
24
Plan/react/dashboard/src/components/input/MultiSelect.js
Normal file
24
Plan/react/dashboard/src/components/input/MultiSelect.js
Normal file
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
const MultiSelect = ({options, selectedIndexes, setSelectedIndexes}) => {
|
||||
const handleChange = (event) => {
|
||||
const renderedOptions = Object.values(event.target.selectedOptions)
|
||||
.map(htmlElement => htmlElement.text)
|
||||
.map(option => options.indexOf(option));
|
||||
setSelectedIndexes(renderedOptions);
|
||||
}
|
||||
|
||||
return (
|
||||
<select className="form-control" multiple
|
||||
onChange={handleChange}>
|
||||
{options.map((option, i) => {
|
||||
return (
|
||||
<option key={i} value={selectedIndexes.includes(i)}
|
||||
selected={selectedIndexes.includes(i)}>{option}</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
)
|
||||
};
|
||||
|
||||
export default MultiSelect
|
@ -14,11 +14,11 @@ import {TableRow} from "./TableRow";
|
||||
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
||||
import {faEye} from "@fortawesome/free-regular-svg-icons";
|
||||
import AsNumbersTable from "./AsNumbersTable";
|
||||
import {CardLoader} from "../navigation/Loader";
|
||||
import {ChartLoader} from "../navigation/Loader";
|
||||
|
||||
const PerformanceAsNumbersTable = ({data}) => {
|
||||
const {t} = useTranslation();
|
||||
if (!data) return <CardLoader/>;
|
||||
if (!data) return <ChartLoader/>;
|
||||
|
||||
return (
|
||||
<AsNumbersTable
|
||||
@ -31,12 +31,19 @@ const PerformanceAsNumbersTable = ({data}) => {
|
||||
data.low_tps_spikes_24h
|
||||
]}/>
|
||||
<TableRow icon={faPowerOff} color="red"
|
||||
text={t('html.label.serverDowntime') + ' (' + t('generic.noData') + ')'}
|
||||
text={t(data.avg_server_downtime_30d ? 'html.label.serverDowntime' : 'html.label.totalServerDowntime') + ' (' + t('generic.noData') + ')'}
|
||||
values={[
|
||||
data.server_downtime_30d,
|
||||
data.server_downtime_7d,
|
||||
data.server_downtime_24h
|
||||
]}/>
|
||||
<TableRow icon={faPowerOff} color="red"
|
||||
text={t('html.label.averageServerDowntime')}
|
||||
values={[
|
||||
data.avg_server_downtime_30d,
|
||||
data.avg_server_downtime_7d,
|
||||
data.avg_server_downtime_24h
|
||||
]}/>
|
||||
<TableRow icon={faUser} color="light-blue" text={t('html.label.averagePlayers')}
|
||||
values={[
|
||||
data.players_30d,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {createContext, useCallback, useContext, useEffect, useState} from "react";
|
||||
import {fetchPlanMetadata} from "../service/metadataService";
|
||||
import {fetchNetworkMetadata, fetchPlanMetadata} from "../service/metadataService";
|
||||
|
||||
import terminal from '../Terminal-icon.png'
|
||||
|
||||
@ -13,6 +13,12 @@ export const MetadataContextProvider = ({children}) => {
|
||||
const {data, error} = await fetchPlanMetadata();
|
||||
if (data) {
|
||||
setMetadata(data);
|
||||
if (data.isProxy) {
|
||||
const {data: networkMetadata} = await fetchNetworkMetadata(); // error ignored
|
||||
if (networkMetadata) {
|
||||
setMetadata({...data, networkMetadata})
|
||||
}
|
||||
}
|
||||
} else if (error) {
|
||||
setMetadata({metadataError: error})
|
||||
}
|
||||
|
@ -29,3 +29,8 @@ export const fetchNetworkPingTable = async (timestamp) => {
|
||||
const url = `/v1/network/pingTable?timestamp=${timestamp}`;
|
||||
return doGetRequest(url);
|
||||
}
|
||||
|
||||
export const fetchNetworkPerformanceOverview = async (timestamp, serverUUIDs) => {
|
||||
const url = `/v1/network/performanceOverview?servers=${encodeURIComponent(JSON.stringify(serverUUIDs))}×tamp=${timestamp}`;
|
||||
return doGetRequest(url);
|
||||
}
|
@ -107,8 +107,8 @@ export const fetchGeolocations = async (timestamp, identifier) => {
|
||||
return doGetRequest(url);
|
||||
}
|
||||
|
||||
export const fetchOptimizedPerformance = async (timestamp, identifier) => {
|
||||
const url = `/v1/graph?type=optimizedPerformance&server=${identifier}×tamp=${timestamp}`;
|
||||
export const fetchOptimizedPerformance = async (timestamp, identifier, after) => {
|
||||
const url = `/v1/graph?type=optimizedPerformance&server=${identifier}×tamp=${timestamp}&after=${after}`;
|
||||
return doGetRequest(url);
|
||||
}
|
||||
|
||||
|
@ -23,3 +23,98 @@ export const tooltip = {
|
||||
twoDecimals: {valueDecimals: 2},
|
||||
zeroDecimals: {valueDecimals: 0}
|
||||
}
|
||||
|
||||
export const mapPerformanceDataToSeries = performanceData => {
|
||||
const playersOnline = [];
|
||||
const tps = [];
|
||||
const cpu = [];
|
||||
const ram = [];
|
||||
const entities = [];
|
||||
const chunks = [];
|
||||
const disk = [];
|
||||
|
||||
return new Promise((resolve => {
|
||||
let i = 0;
|
||||
const length = performanceData.length;
|
||||
|
||||
function processNextThousand() {
|
||||
const to = Math.min(i + 1000, length);
|
||||
for (i; i < to; i++) {
|
||||
const entry = performanceData[i];
|
||||
const date = entry[0];
|
||||
playersOnline[i] = [date, entry[1]];
|
||||
tps[i] = [date, entry[2]];
|
||||
cpu[i] = [date, entry[3]];
|
||||
ram[i] = [date, entry[4]];
|
||||
entities[i] = [date, entry[5]];
|
||||
chunks[i] = [date, entry[6]];
|
||||
disk[i] = [date, entry[7]];
|
||||
}
|
||||
if (i >= length) {
|
||||
resolve({playersOnline, tps, cpu, ram, entities, chunks, disk})
|
||||
} else {
|
||||
setTimeout(processNextThousand, 10);
|
||||
}
|
||||
}
|
||||
|
||||
processNextThousand();
|
||||
}))
|
||||
};
|
||||
|
||||
export const yAxisConfigurations = {
|
||||
PLAYERS_ONLINE: {
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' P';
|
||||
}
|
||||
},
|
||||
softMin: 0,
|
||||
softMax: 2
|
||||
},
|
||||
TPS: {
|
||||
opposite: true,
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' TPS';
|
||||
}
|
||||
},
|
||||
softMin: 0,
|
||||
softMax: 20
|
||||
},
|
||||
CPU: {
|
||||
opposite: true,
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + '%';
|
||||
}
|
||||
},
|
||||
softMin: 0,
|
||||
softMax: 100
|
||||
},
|
||||
RAM_OR_DISK: {
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' MB';
|
||||
}
|
||||
},
|
||||
softMin: 0
|
||||
},
|
||||
ENTITIES: {
|
||||
opposite: true,
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' E';
|
||||
}
|
||||
},
|
||||
softMin: 0,
|
||||
softMax: 2
|
||||
},
|
||||
CHUNKS: {
|
||||
labels: {
|
||||
formatter: function () {
|
||||
return this.value + ' C';
|
||||
}
|
||||
},
|
||||
softMin: 0
|
||||
}
|
||||
}
|
@ -25,17 +25,14 @@ import {faCalendarCheck} from "@fortawesome/free-regular-svg-icons";
|
||||
import {SwitchTransition} from "react-transition-group";
|
||||
import MainPageRedirect from "../../components/navigation/MainPageRedirect";
|
||||
import {ServerExtensionContextProvider, useServerExtensionContext} from "../../hooks/serverExtensionDataContext";
|
||||
import {useDataRequest} from "../../hooks/dataFetchHook";
|
||||
import {fetchNetworkMetadata} from "../../service/metadataService";
|
||||
import {iconTypeToFontAwesomeClass} from "../../util/icons";
|
||||
|
||||
const NetworkSidebar = () => {
|
||||
const {t, i18n} = useTranslation();
|
||||
const {sidebarItems, setSidebarItems} = useNavigation();
|
||||
const {networkMetadata} = useMetadata();
|
||||
const {extensionData} = useServerExtensionContext();
|
||||
|
||||
const {data: networkMetadata} = useDataRequest(fetchNetworkMetadata, [])
|
||||
|
||||
useEffect(() => {
|
||||
const servers = networkMetadata?.servers || [];
|
||||
const items = [
|
||||
|
119
Plan/react/dashboard/src/views/network/NetworkPerformance.js
Normal file
119
Plan/react/dashboard/src/views/network/NetworkPerformance.js
Normal file
@ -0,0 +1,119 @@
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import LoadIn from "../../components/animation/LoadIn";
|
||||
import {Card, Col, Row} from "react-bootstrap-v5";
|
||||
import {useMetadata} from "../../hooks/metadataHook";
|
||||
import CardHeader from "../../components/cards/CardHeader";
|
||||
import {faServer} from "@fortawesome/free-solid-svg-icons";
|
||||
import MultiSelect from "../../components/input/MultiSelect";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {fetchOptimizedPerformance} from "../../service/serverService";
|
||||
import {fetchNetworkPerformanceOverview} from "../../service/networkService";
|
||||
import PerformanceAsNumbersCard from "../../components/cards/server/tables/PerformanceAsNumbersCard";
|
||||
import {useNavigation} from "../../hooks/navigationHook";
|
||||
import {mapPerformanceDataToSeries} from "../../util/graphs";
|
||||
import PerformanceGraphsCard from "../../components/cards/network/PerformanceGraphsCard";
|
||||
|
||||
const NetworkPerformance = () => {
|
||||
const {t} = useTranslation();
|
||||
const {networkMetadata} = useMetadata();
|
||||
const {updateRequested} = useNavigation();
|
||||
|
||||
const [serverOptions, setServerOptions] = useState([]);
|
||||
const [selectedOptions, setSelectedOptions] = useState([]);
|
||||
const [visualizedServers, setVisualizedServers] = useState([]);
|
||||
|
||||
const initializeServerOptions = () => {
|
||||
if (networkMetadata) {
|
||||
const options = networkMetadata.servers;
|
||||
setServerOptions(options);
|
||||
|
||||
const indexOfProxy = options
|
||||
.findIndex(option => option.serverName === networkMetadata.currentServer.serverName);
|
||||
|
||||
setSelectedOptions([indexOfProxy]);
|
||||
setVisualizedServers([indexOfProxy]);
|
||||
}
|
||||
};
|
||||
useEffect(initializeServerOptions, [networkMetadata, setVisualizedServers]);
|
||||
|
||||
const applySelected = () => {
|
||||
setVisualizedServers(selectedOptions);
|
||||
}
|
||||
|
||||
const [performanceData, setPerformanceData] = useState({});
|
||||
const loadPerformanceData = useCallback(async () => {
|
||||
const loaded = {
|
||||
servers: [],
|
||||
data: [],
|
||||
values: [],
|
||||
errors: [],
|
||||
zones: {},
|
||||
colors: {},
|
||||
timestamp_f: ''
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
const monthMs = 2592000000;
|
||||
const after = time - monthMs;
|
||||
|
||||
for (const index of visualizedServers) {
|
||||
const server = serverOptions[index];
|
||||
|
||||
const {data, error} = await fetchOptimizedPerformance(time, encodeURIComponent(server.serverUUID), after);
|
||||
if (data) {
|
||||
loaded.servers.push(server);
|
||||
const values = data.values;
|
||||
delete data.values;
|
||||
loaded.data.push(data);
|
||||
loaded.values.push(await mapPerformanceDataToSeries(values));
|
||||
loaded.zones = data.zones;
|
||||
loaded.colors = data.colors;
|
||||
loaded.timestamp_f = data.timestamp_f;
|
||||
} else if (error) {
|
||||
loaded.errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedUUIDs = visualizedServers
|
||||
.map(index => serverOptions[index])
|
||||
.map(server => server.serverUUID);
|
||||
const {data, error} = await fetchNetworkPerformanceOverview(time, selectedUUIDs);
|
||||
if (error) loaded.errors.push(error);
|
||||
|
||||
setPerformanceData({...loaded, overview: data});
|
||||
}, [visualizedServers, serverOptions, setPerformanceData])
|
||||
|
||||
useEffect(() => {
|
||||
loadPerformanceData();
|
||||
}, [loadPerformanceData, visualizedServers, updateRequested]);
|
||||
|
||||
const isUpToDate = visualizedServers.every((s, i) => s === selectedOptions[i]);
|
||||
return (
|
||||
<LoadIn>
|
||||
<section className={"network_performance"}>
|
||||
<Row>
|
||||
<Col>
|
||||
<PerformanceGraphsCard data={performanceData}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col md={8}>
|
||||
<PerformanceAsNumbersCard data={performanceData?.overview?.numbers}/>
|
||||
</Col>
|
||||
<Col md={4}>
|
||||
<Card>
|
||||
<CardHeader icon={faServer} color={'light-green'} label={t('html.label.serverSelector')}/>
|
||||
<MultiSelect options={serverOptions.map(server => server.serverName)}
|
||||
selectedIndexes={selectedOptions}
|
||||
setSelectedIndexes={setSelectedOptions}/>
|
||||
<button className={'btn bg-transparent'} onClick={applySelected} disabled={isUpToDate}>
|
||||
{t('html.label.apply')}
|
||||
</button>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</section>
|
||||
</LoadIn>
|
||||
)
|
||||
};
|
||||
|
||||
export default NetworkPerformance
|
Loading…
Reference in New Issue
Block a user