Implement Network overview tab in React

This commit is contained in:
Aurora Lahtela 2022-09-04 09:40:17 +03:00
parent a095eb2178
commit 6d9fcab656
14 changed files with 214 additions and 64 deletions

View File

@ -34,6 +34,7 @@ const ServerPluginData = React.lazy(() => import("./views/server/ServerPluginDat
const ServerWidePluginData = React.lazy(() => import("./views/server/ServerWidePluginData"));
const NetworkPage = React.lazy(() => import("./views/layout/NetworkPage"));
const NetworkOverview = React.lazy(() => import("./views/network/NetworkOverview"));
const PlayersPage = React.lazy(() => import("./views/layout/PlayersPage"));
const AllPlayers = React.lazy(() => import("./views/players/AllPlayers"));
@ -113,6 +114,7 @@ function App() {
</Route>
<Route path="/network" element={<Lazy><NetworkPage/></Lazy>}>
<Route path="" element={<Lazy><OverviewRedirect/></Lazy>}/>
<Route path="overview" element={<Lazy><NetworkOverview/></Lazy>}/>
<Route path="players" element={<Lazy><AllPlayers/></Lazy>}/>
<Route path="plugins-overview" element={<Lazy><ServerPluginData/></Lazy>}/>
<Route path="plugins/:plugin" element={<Lazy><ServerWidePluginData/></Lazy>}/>

View File

@ -3,6 +3,8 @@ import React from "react";
import End from "./layout/End";
const Datapoint = ({icon, color, name, value, valueLabel, bold, boldTitle, title, trend}) => {
if (value === undefined && valueLabel === undefined) return <></>;
const displayedValue = bold ? <b>{value}</b> : value;
const extraLabel = typeof valueLabel === 'string' ? ` (${valueLabel})` : '';
const colorClass = color && color.startsWith("col-") ? color : "col-" + color;

View File

@ -0,0 +1,60 @@
import React from 'react';
import {useTranslation} from "react-i18next";
import {Card} from "react-bootstrap-v5";
import CardTabs from "../../../CardTabs";
import {faChartArea} from "@fortawesome/free-solid-svg-icons";
import {useDataRequest} from "../../../../hooks/dataFetchHook";
import {fetchDayByDayGraph, fetchHourByHourGraph, fetchPlayersOnlineGraph} from "../../../../service/serverService";
import {ErrorViewBody} from "../../../../views/ErrorView";
import {ChartLoader} from "../../../navigation/Loader";
import TimeByTimeGraph from "../../../graphs/TimeByTimeGraph";
import PlayersOnlineGraph from "../../../graphs/PlayersOnlineGraph";
import {useMetadata} from "../../../../hooks/metadataHook";
const PlayersOnlineTab = () => {
const {serverUUID} = useMetadata();
const {data, loadingError} = useDataRequest(fetchPlayersOnlineGraph, [serverUUID]);
if (loadingError) return <ErrorViewBody error={loadingError}/>
if (!data) return <ChartLoader/>;
return <PlayersOnlineGraph data={data}/>
}
const DayByDayTab = () => {
const {data, loadingError} = useDataRequest(fetchDayByDayGraph, [])
if (loadingError) return <ErrorViewBody error={loadingError}/>
if (!data) return <ChartLoader/>;
return <TimeByTimeGraph data={data}/>
}
const HourByHourTab = () => {
const {data, loadingError} = useDataRequest(fetchHourByHourGraph, [])
if (loadingError) return <ErrorViewBody error={loadingError}/>
if (!data) return <ChartLoader/>;
return <TimeByTimeGraph data={data}/>
}
const NetworkOnlineActivityGraphsCard = () => {
const {t} = useTranslation();
return <Card>
<CardTabs tabs={[
{
name: t('html.label.networkOnlineActivity'), icon: faChartArea, color: 'blue', href: 'online-activity',
element: <PlayersOnlineTab/>
}, {
name: t('html.label.dayByDay'), icon: faChartArea, color: 'blue', href: 'day-by-day',
element: <DayByDayTab/>
}, {
name: t('html.label.hourByHour'), icon: faChartArea, color: 'blue', href: 'hour-by-hour',
element: <HourByHourTab/>
}
]}/>
</Card>
};
export default NetworkOnlineActivityGraphsCard

View File

@ -31,6 +31,10 @@ const ServerWeekComparisonCard = ({data}) => {
text={t('html.label.averagePlaytime') + ' ' + t('html.label.perPlayer')}
values={[data.average_playtime_before, data.average_playtime_after,
<BigTrend trend={data.average_playtime_trend}/>]}/>
<TableRow icon={faClock} color="teal"
text={t('html.label.averageSessionLength')}
values={[data.session_length_average_before, data.session_length_average_after,
<BigTrend trend={data.session_length_average_trend}/>]}/>
<TableRow icon={faCalendarCheck} color="teal" text={t('html.label.sessions')}
values={[data.sessions_before, data.sessions_after,
<BigTrend trend={data.sessions_trend}/>]}/>

View File

@ -24,7 +24,7 @@ const ServerAsNumbersCard = ({data}) => {
<Card>
<Card.Header>
<h6 className="col-black">
<Fa icon={faBookOpen}/> {t('html.label.serverAsNumberse')}
<Fa icon={faBookOpen}/> {data.player_kills ? t('html.label.serverAsNumberse') : t('html.label.networkAsNumbers')}
</h6>
</Card.Header>
<Card.Body>
@ -55,10 +55,13 @@ const ServerAsNumbersCard = ({data}) => {
<Datapoint name={t('html.label.averagePlaytime') + ' ' + t('html.label.perPlayer')}
color={'green'} icon={faClock}
value={data.player_playtime}/>
<Datapoint name={t('html.label.averageSessionLength')}
color={'teal'} icon={faClock}
value={data.session_length_avg}/>
<Datapoint name={t('html.label.sessions')}
color={'teal'} icon={faCalendarCheck}
value={data.sessions} bold/>
<hr/>
{data.player_kills && <hr/>}
<Datapoint name={t('html.label.playerKills')}
color={'red'} icon={faCrosshairs}
value={data.player_kills} bold/>

View File

@ -3,6 +3,7 @@ import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
import {faAngleRight, faSkullCrossbones} from "@fortawesome/free-solid-svg-icons";
import {useTheme} from "../../hooks/themeHook";
import {useTranslation} from "react-i18next";
import Scrollable from "../Scrollable";
const KillRow = ({kill}) => {
const killSeparator = <Fa
@ -23,16 +24,18 @@ const KillsTable = ({kills}) => {
const {nightModeEnabled} = useTheme();
return (
<table className={"table mb-0" + (nightModeEnabled ? " table-dark" : '')}>
<tbody>
{kills.length ? kills.map((kill, i) => <KillRow key={i} kill={kill}/>) : <tr>
<td>{t('html.generic.none')}</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>}
</tbody>
</table>
<Scrollable>
<table className={"table mb-0" + (nightModeEnabled ? " table-dark" : '')}>
<tbody>
{kills.length ? kills.map((kill, i) => <KillRow key={i} kill={kill}/>) : <tr>
<td>{t('html.generic.none')}</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>}
</tbody>
</table>
</Scrollable>
)
};

View File

@ -2,6 +2,8 @@ import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
import React from "react";
export const TableRow = ({icon, text, color, values, bold}) => {
if (!values || values.filter(value => value !== undefined).length < values.length) return <></>;
const label = (<><Fa icon={icon} className={'col-' + color}/> {text}</>);
return (
<tr>

View File

@ -8,7 +8,7 @@ export const useDataRequest = (fetchMethod, parameters) => {
/*eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
fetchMethod(...parameters, updateRequested).then(({data: json, error}) => {
fetchMethod(updateRequested, ...parameters).then(({data: json, error}) => {
if (json) {
setData(json);
finishUpdate(json.timestamp, json.timestamp_f);

View File

@ -0,0 +1,6 @@
import {doGetRequest} from "./backendConfiguration";
export const fetchNetworkOverview = async (updateRequested) => {
const url = `/v1/network/overview?timestamp=${updateRequested}`;
return doGetRequest(url);
}

View File

@ -1,7 +1,7 @@
import {faMapSigns} from "@fortawesome/free-solid-svg-icons";
import {doSomeGetRequest, standard200option} from "./backendConfiguration";
export const fetchPlayer = async (uuid, timestamp) => {
export const fetchPlayer = async (timestamp, uuid) => {
const url = `/v1/player?player=${uuid}&timestamp=${timestamp}`;
return doSomeGetRequest(url, [
standard200option,

View File

@ -1,133 +1,116 @@
import {doGetRequest} from "./backendConfiguration";
export const fetchServerIdentity = async (identifier) => {
export const fetchServerIdentity = async (timestamp, identifier) => {
const url = `/v1/serverIdentity?server=${identifier}`;
return doGetRequest(url);
}
export const fetchServerOverview = async (identifier) => {
const timestamp = Date.now();
export const fetchServerOverview = async (timestamp, identifier) => {
const url = `/v1/serverOverview?server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchOnlineActivityOverview = async (identifier) => {
const timestamp = Date.now();
export const fetchOnlineActivityOverview = async (timestamp, identifier) => {
const url = `/v1/onlineOverview?server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchPlayerbaseOverview = async (identifier) => {
const timestamp = Date.now();
export const fetchPlayerbaseOverview = async (timestamp, identifier) => {
const url = `/v1/playerbaseOverview?server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchSessionOverview = async (identifier) => {
const timestamp = Date.now();
export const fetchSessionOverview = async (timestamp, identifier) => {
const url = `/v1/sessionsOverview?server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchPvpPve = async (identifier) => {
const timestamp = Date.now();
export const fetchPvpPve = async (timestamp, identifier) => {
const url = `/v1/playerVersus?server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchPerformanceOverview = async (identifier) => {
const timestamp = Date.now();
export const fetchPerformanceOverview = async (timestamp, identifier) => {
const url = `/v1/performanceOverview?server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchExtensionData = async (identifier) => {
const timestamp = Date.now();
export const fetchExtensionData = async (timestamp, identifier) => {
const url = `/v1/extensionData?server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchSessions = async (identifier) => {
const timestamp = Date.now();
export const fetchSessions = async (timestamp, identifier) => {
const url = `/v1/sessions?server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchKills = async (identifier) => {
const timestamp = Date.now();
export const fetchKills = async (timestamp, identifier) => {
const url = `/v1/kills?server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchPlayers = async (identifier) => {
const timestamp = Date.now();
export const fetchPlayers = async (timestamp, identifier) => {
const url = identifier ? `/v1/players?server=${identifier}&timestamp=${timestamp}` : `/v1/players?timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchPingTable = async (identifier) => {
const timestamp = Date.now();
export const fetchPingTable = async (timestamp, identifier) => {
const url = identifier ? `/v1/pingTable?server=${identifier}&timestamp=${timestamp}` : `/v1/pingTable?timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchPlayersOnlineGraph = async (identifier) => {
const timestamp = Date.now();
const url = `/v1/graph?type=playersOnline&server=${identifier}&timestamp=${timestamp}`;
export const fetchPlayersOnlineGraph = async (timestamp, identifier) => {
const url = identifier ? `/v1/graph?type=playersOnline&server=${identifier}&timestamp=${timestamp}` :
`/v1/graph?type=playersOnline&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchPlayerbaseDevelopmentGraph = async (identifier) => {
const timestamp = Date.now();
const url = `/v1/graph?type=activity&server=${identifier}&timestamp=${timestamp}`;
export const fetchPlayerbaseDevelopmentGraph = async (timestamp, identifier) => {
const url = identifier ? `/v1/graph?type=activity&server=${identifier}&timestamp=${timestamp}` :
`/v1/graph?type=activity&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchDayByDayGraph = async (identifier) => {
const timestamp = Date.now();
const url = `/v1/graph?type=uniqueAndNew&server=${identifier}&timestamp=${timestamp}`;
export const fetchDayByDayGraph = async (timestamp, identifier) => {
const url = identifier ? `/v1/graph?type=uniqueAndNew&server=${identifier}&timestamp=${timestamp}` :
`/v1/graph?type=uniqueAndNew&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchHourByHourGraph = async (identifier) => {
const timestamp = Date.now();
const url = `/v1/graph?type=hourlyUniqueAndNew&server=${identifier}&timestamp=${timestamp}`;
export const fetchHourByHourGraph = async (timestamp, identifier) => {
const url = identifier ? `/v1/graph?type=hourlyUniqueAndNew&server=${identifier}&timestamp=${timestamp}` :
`/v1/graph?type=hourlyUniqueAndNew&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchServerCalendarGraph = async (identifier) => {
const timestamp = Date.now();
export const fetchServerCalendarGraph = async (timestamp, identifier) => {
const url = `/v1/graph?type=serverCalendar&server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchPunchCardGraph = async (identifier) => {
const timestamp = Date.now();
export const fetchPunchCardGraph = async (timestamp, identifier) => {
const url = `/v1/graph?type=punchCard&server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchWorldPie = async (identifier) => {
const timestamp = Date.now();
export const fetchWorldPie = async (timestamp, identifier) => {
const url = `/v1/graph?type=worldPie&server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchGeolocations = async (identifier) => {
const timestamp = Date.now();
export const fetchGeolocations = async (timestamp, identifier) => {
const url = `/v1/graph?type=geolocation&server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchOptimizedPerformance = async (identifier) => {
const timestamp = Date.now();
export const fetchOptimizedPerformance = async (timestamp, identifier) => {
const url = `/v1/graph?type=optimizedPerformance&server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchPingGraph = async (identifier) => {
const timestamp = Date.now();
export const fetchPingGraph = async (timestamp, identifier) => {
const url = `/v1/graph?type=aggregatedPing&server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}

View File

@ -44,7 +44,12 @@ const NetworkSidebar = () => {
name: 'html.label.servers',
icon: faServer,
contents: [
{name: 'html.label.overview', icon: faNetworkWired, href: "serversOverview"},
{
nameShort: 'html.label.overview',
name: 'html.label.servers',
icon: faNetworkWired,
href: "serversOverview"
},
{name: 'html.label.sessions', icon: faCalendarCheck, href: "sessions"},
{name: 'html.label.performance', icon: faCogs, href: "performance"},
{},

View File

@ -20,9 +20,9 @@ const PlayerPage = () => {
const {sidebarItems, setSidebarItems} = useNavigation();
const {identifier} = useParams();
const {currentTab, updateRequested, finishUpdate} = useNavigation();
const {currentTab, finishUpdate} = useNavigation();
const {data: player, loadingError} = useDataRequest(fetchPlayer, [identifier, updateRequested])
const {data: player, loadingError} = useDataRequest(fetchPlayer, [identifier])
useEffect(() => {
if (!player) return;

View File

@ -0,0 +1,80 @@
import React from 'react';
import {useDataRequest} from "../../hooks/dataFetchHook";
import ErrorView from "../ErrorView";
import LoadIn from "../../components/animation/LoadIn";
import {Card, Col, Row} from "react-bootstrap-v5";
import ServerAsNumbersCard from "../../components/cards/server/values/ServerAsNumbersCard";
import ServerWeekComparisonCard from "../../components/cards/server/tables/ServerWeekComparisonCard";
import {fetchNetworkOverview} from "../../service/networkService";
import {useTranslation} from "react-i18next";
import {CardLoader} from "../../components/navigation/Loader";
import Datapoint from "../../components/Datapoint";
import {faUsers} from "@fortawesome/free-solid-svg-icons";
import NetworkOnlineActivityGraphsCard from "../../components/cards/server/graphs/NetworkOnlineActivityGraphsCard";
const RecentPlayersCard = ({data}) => {
const {t} = useTranslation();
if (!data) return <CardLoader/>;
return (
<Card>
<Card.Header>
<h6 className="col-black">
{t('html.label.players')}
</h6>
</Card.Header>
<Card.Body>
<p>{t('html.label.last24hours')}</p>
<Datapoint icon={faUsers} color="light-blue"
name={t('html.label.uniquePlayers')} value={data.unique_players_1d}/>
<Datapoint icon={faUsers} color="light-green"
name={t('html.label.newPlayers')} value={data.new_players_1d}/>
<p>{t('html.label.last7days')}</p>
<Datapoint icon={faUsers} color="light-blue"
name={t('html.label.uniquePlayers')} value={data.unique_players_7d}/>
<Datapoint icon={faUsers} color="light-green"
name={t('html.label.newPlayers')} value={data.new_players_7d}/>
<p>{t('html.label.last30days')}</p>
<Datapoint icon={faUsers} color="light-blue"
name={t('html.label.uniquePlayers')} value={data.unique_players_30d}/>
<Datapoint icon={faUsers} color="light-green"
name={t('html.label.newPlayers')} value={data.new_players_30d}/>
</Card.Body>
</Card>
)
}
const NetworkOverview = () => {
const {data, loadingError} = useDataRequest(fetchNetworkOverview, [])
if (loadingError) {
return <ErrorView error={loadingError}/>
}
return (
<LoadIn>
<section className="network_overview">
<Row>
<Col lg={9}>
<NetworkOnlineActivityGraphsCard/>
</Col>
<Col lg={3}>
<RecentPlayersCard data={data?.players}/>
</Col>
</Row>
<Row>
<Col lg={4}>
<ServerAsNumbersCard data={data?.numbers}/>
</Col>
<Col lg={8}>
<ServerWeekComparisonCard data={data?.weeks}/>
</Col>
</Row>
</section>
</LoadIn>
)
};
export default NetworkOverview