mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-12-27 09:00:28 +08:00
Implemented network page join address tab
- Made it possible to toggle stack/side-by-side join addresses
This commit is contained in:
parent
ab2dfbbbcf
commit
7a51633690
@ -464,6 +464,17 @@ public class GraphJSONCreator {
|
|||||||
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
|
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
|
||||||
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(serverUUID, config.getTimeZone().getOffset(System.currentTimeMillis()), after, before));
|
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(serverUUID, config.getTimeZone().getOffset(System.currentTimeMillis()), after, before));
|
||||||
|
|
||||||
|
return mapToJson(pieColors, joinAddresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> joinAddressesByDay(long after, long before) {
|
||||||
|
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
|
||||||
|
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(config.getTimeZone().getOffset(System.currentTimeMillis()), after, before));
|
||||||
|
|
||||||
|
return mapToJson(pieColors, joinAddresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> mapToJson(String[] pieColors, List<DateObj<Map<String, Integer>>> joinAddresses) {
|
||||||
for (DateObj<Map<String, Integer>> addressesByDate : joinAddresses) {
|
for (DateObj<Map<String, Integer>> addressesByDate : joinAddresses) {
|
||||||
translateUnknown(addressesByDate.getValue());
|
translateUnknown(addressesByDate.getValue());
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ public class GraphsJSONResolver implements Resolver {
|
|||||||
} else {
|
} else {
|
||||||
// Assume network
|
// Assume network
|
||||||
storedJSON = jsonResolverService.resolve(
|
storedJSON = jsonResolverService.resolve(
|
||||||
timestamp, dataID, () -> generateGraphDataJSONOfType(dataID)
|
timestamp, dataID, () -> generateGraphDataJSONOfType(dataID, request.getQuery())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return storedJSON;
|
return storedJSON;
|
||||||
@ -226,7 +226,7 @@ public class GraphsJSONResolver implements Resolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object generateGraphDataJSONOfType(DataID id) {
|
private Object generateGraphDataJSONOfType(DataID id, URIQuery query) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case GRAPH_ACTIVITY:
|
case GRAPH_ACTIVITY:
|
||||||
return graphJSON.activityGraphsJSONAsMap();
|
return graphJSON.activityGraphsJSONAsMap();
|
||||||
@ -240,6 +240,15 @@ public class GraphsJSONResolver implements Resolver {
|
|||||||
return graphJSON.playerHostnamePieJSONAsMap();
|
return graphJSON.playerHostnamePieJSONAsMap();
|
||||||
case GRAPH_WORLD_MAP:
|
case GRAPH_WORLD_MAP:
|
||||||
return graphJSON.geolocationGraphsJSONAsMap();
|
return graphJSON.geolocationGraphsJSONAsMap();
|
||||||
|
case JOIN_ADDRESSES_BY_DAY:
|
||||||
|
try {
|
||||||
|
return graphJSON.joinAddressesByDay(
|
||||||
|
query.get("after").map(Long::parseLong).orElse(0L),
|
||||||
|
query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis())
|
||||||
|
);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number) " + e.getMessage());
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return Collections.singletonMap("error", "Undefined ID: " + id.name());
|
return Collections.singletonMap("error", "Undefined ID: " + id.name());
|
||||||
}
|
}
|
||||||
|
@ -251,6 +251,7 @@ public enum HtmlLang implements Lang {
|
|||||||
LABEL_TOTAL("html.label.total", "Total"),
|
LABEL_TOTAL("html.label.total", "Total"),
|
||||||
LABEL_ALPHABETICAL("html.label.alphabetical", "Alphabetical"),
|
LABEL_ALPHABETICAL("html.label.alphabetical", "Alphabetical"),
|
||||||
LABEL_SORT_BY("html.label.sortBy", "Sort By"),
|
LABEL_SORT_BY("html.label.sortBy", "Sort By"),
|
||||||
|
LABEL_STACKED("html.label.stacked", "Stacked"),
|
||||||
|
|
||||||
LOGIN_LOGIN("html.login.login", "Login"),
|
LOGIN_LOGIN("html.login.login", "Login"),
|
||||||
LOGIN_LOGOUT("html.login.logout", "Logout"),
|
LOGIN_LOGOUT("html.login.logout", "Logout"),
|
||||||
|
@ -158,4 +158,46 @@ public class JoinAddressQueries {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(long timezoneOffset, long after, long before) {
|
||||||
|
return db -> {
|
||||||
|
Sql sql = db.getSql();
|
||||||
|
|
||||||
|
String selectAddresses = SELECT +
|
||||||
|
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
|
||||||
|
"*1000 as date," +
|
||||||
|
JoinAddressTable.JOIN_ADDRESS +
|
||||||
|
", COUNT(1) as count" +
|
||||||
|
FROM + SessionsTable.TABLE_NAME + " s" +
|
||||||
|
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
|
||||||
|
WHERE + SessionsTable.SESSION_START + ">?" +
|
||||||
|
AND + SessionsTable.SESSION_START + "<=?" +
|
||||||
|
GROUP_BY + "date,j." + JoinAddressTable.ID;
|
||||||
|
|
||||||
|
return db.query(new QueryStatement<>(selectAddresses, 1000) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
statement.setLong(1, timezoneOffset);
|
||||||
|
statement.setLong(2, after);
|
||||||
|
statement.setLong(3, before);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DateObj<Map<String, Integer>>> processResults(ResultSet set) throws SQLException {
|
||||||
|
Map<Long, Map<String, Integer>> addressesByDate = new HashMap<>();
|
||||||
|
while (set.next()) {
|
||||||
|
long date = set.getLong("date");
|
||||||
|
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
|
||||||
|
int count = set.getInt("count");
|
||||||
|
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
|
||||||
|
joinAddresses.put(joinAddress, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
return addressesByDate.entrySet()
|
||||||
|
.stream().map(entry -> new DateObj<>(entry.getKey(), entry.getValue()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ const NetworkPage = React.lazy(() => import("./views/layout/NetworkPage"));
|
|||||||
const NetworkOverview = React.lazy(() => import("./views/network/NetworkOverview"));
|
const NetworkOverview = React.lazy(() => import("./views/network/NetworkOverview"));
|
||||||
const NetworkServers = React.lazy(() => import("./views/network/NetworkServers"));
|
const NetworkServers = React.lazy(() => import("./views/network/NetworkServers"));
|
||||||
const NetworkSessions = React.lazy(() => import("./views/network/NetworkSessions"));
|
const NetworkSessions = React.lazy(() => import("./views/network/NetworkSessions"));
|
||||||
|
const NetworkJoinAddresses = React.lazy(() => import("./views/network/NetworkJoinAddresses"));
|
||||||
|
|
||||||
const PlayersPage = React.lazy(() => import("./views/layout/PlayersPage"));
|
const PlayersPage = React.lazy(() => import("./views/layout/PlayersPage"));
|
||||||
const AllPlayers = React.lazy(() => import("./views/players/AllPlayers"));
|
const AllPlayers = React.lazy(() => import("./views/players/AllPlayers"));
|
||||||
@ -121,6 +122,7 @@ function App() {
|
|||||||
<Route path="overview" element={<Lazy><NetworkOverview/></Lazy>}/>
|
<Route path="overview" element={<Lazy><NetworkOverview/></Lazy>}/>
|
||||||
<Route path="serversOverview" element={<Lazy><NetworkServers/></Lazy>}/>
|
<Route path="serversOverview" element={<Lazy><NetworkServers/></Lazy>}/>
|
||||||
<Route path="sessions" element={<Lazy><NetworkSessions/></Lazy>}/>
|
<Route path="sessions" element={<Lazy><NetworkSessions/></Lazy>}/>
|
||||||
|
<Route path="join-addresses" element={<Lazy><NetworkJoinAddresses/></Lazy>}/>
|
||||||
<Route path="players" element={<Lazy><AllPlayers/></Lazy>}/>
|
<Route path="players" element={<Lazy><AllPlayers/></Lazy>}/>
|
||||||
<Route path="plugins-overview" element={<Lazy><ServerPluginData/></Lazy>}/>
|
<Route path="plugins-overview" element={<Lazy><ServerPluginData/></Lazy>}/>
|
||||||
<Route path="plugins/:plugin" element={<Lazy><ServerWidePluginData/></Lazy>}/>
|
<Route path="plugins/:plugin" element={<Lazy><ServerWidePluginData/></Lazy>}/>
|
||||||
|
20
Plan/react/dashboard/src/components/Toggle.js
Normal file
20
Plan/react/dashboard/src/components/Toggle.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React, {useState} from 'react';
|
||||||
|
|
||||||
|
const Toggle = ({children, value, onValueChange, color}) => {
|
||||||
|
const [renderTime] = useState(new Date().getTime());
|
||||||
|
const id = 'checkbox-' + renderTime;
|
||||||
|
|
||||||
|
const handleChange = () => {
|
||||||
|
onValueChange(!value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="form-check form-switch">
|
||||||
|
<input id={id} type={"checkbox"} className={"form-check-input bg-" + color} role="switch"
|
||||||
|
onChange={handleChange} checked={value}/>
|
||||||
|
<label className="form-check-label" htmlFor={id}>{children}</label>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Toggle
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React, {useState} from 'react';
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {useParams} from "react-router-dom";
|
|
||||||
import {useDataRequest} from "../../../../hooks/dataFetchHook";
|
import {useDataRequest} from "../../../../hooks/dataFetchHook";
|
||||||
import {fetchJoinAddressByDay} from "../../../../service/serverService";
|
import {fetchJoinAddressByDay} from "../../../../service/serverService";
|
||||||
import {ErrorViewCard} from "../../../../views/ErrorView";
|
import {ErrorViewCard} from "../../../../views/ErrorView";
|
||||||
@ -9,24 +8,28 @@ import {Card} from "react-bootstrap-v5";
|
|||||||
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
||||||
import {faChartColumn} from "@fortawesome/free-solid-svg-icons";
|
import {faChartColumn} from "@fortawesome/free-solid-svg-icons";
|
||||||
import JoinAddressGraph from "../../../graphs/JoinAddressGraph";
|
import JoinAddressGraph from "../../../graphs/JoinAddressGraph";
|
||||||
|
import Toggle from "../../../Toggle";
|
||||||
|
|
||||||
const JoinAddressGraphCard = () => {
|
const JoinAddressGraphCard = ({identifier}) => {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const {identifier} = useParams();
|
const [stack, setStack] = useState(true);
|
||||||
|
|
||||||
const {data, loadingError} = useDataRequest(fetchJoinAddressByDay, [identifier]);
|
const {data, loadingError} = useDataRequest(fetchJoinAddressByDay, [identifier]);
|
||||||
|
|
||||||
if (loadingError) return <ErrorViewCard error={loadingError}/>
|
if (loadingError) return <ErrorViewCard error={loadingError}/>
|
||||||
if (!data) return <CardLoader/>;
|
if (!data) return <CardLoader/>;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
<h6 className="col-black" style={{width: '100%'}}>
|
<h6 className="col-black" style={{width: '100%'}}>
|
||||||
<Fa icon={faChartColumn} className="col-amber"/> {t('html.label.joinAddresses')}
|
<Fa icon={faChartColumn} className="col-amber"/> {t('html.label.joinAddresses')}
|
||||||
</h6>
|
</h6>
|
||||||
|
<Toggle value={stack} onValueChange={setStack} color={'amber'}>{t('html.label.stacked')}</Toggle>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<JoinAddressGraph id={'join-address-graph'} data={data?.join_addresses_by_date} colors={data?.colors}/>
|
<JoinAddressGraph id={'join-address-graph'} data={data?.join_addresses_by_date} colors={data?.colors}
|
||||||
|
stack={stack}/>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {useParams} from "react-router-dom";
|
|
||||||
import {useDataRequest} from "../../../../hooks/dataFetchHook";
|
import {useDataRequest} from "../../../../hooks/dataFetchHook";
|
||||||
import {fetchJoinAddressPie} from "../../../../service/serverService";
|
import {fetchJoinAddressPie} from "../../../../service/serverService";
|
||||||
import {ErrorViewCard} from "../../../../views/ErrorView";
|
import {ErrorViewCard} from "../../../../views/ErrorView";
|
||||||
@ -10,9 +9,8 @@ import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
|||||||
import {faLocationArrow} from "@fortawesome/free-solid-svg-icons";
|
import {faLocationArrow} from "@fortawesome/free-solid-svg-icons";
|
||||||
import GroupVisualizer from "../../../graphs/GroupVisualizer";
|
import GroupVisualizer from "../../../graphs/GroupVisualizer";
|
||||||
|
|
||||||
const JoinAddressGroupCard = () => {
|
const JoinAddressGroupCard = ({identifier}) => {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const {identifier} = useParams();
|
|
||||||
|
|
||||||
const {data, loadingError} = useDataRequest(fetchJoinAddressPie, [identifier]);
|
const {data, loadingError} = useDataRequest(fetchJoinAddressPie, [identifier]);
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import Highcharts from "highcharts/highstock";
|
|||||||
import Accessibility from "highcharts/modules/accessibility";
|
import Accessibility from "highcharts/modules/accessibility";
|
||||||
import {linegraphButtons} from "../../util/graphs";
|
import {linegraphButtons} from "../../util/graphs";
|
||||||
|
|
||||||
const JoinAddressGraph = ({id, data, colors}) => {
|
const JoinAddressGraph = ({id, data, colors, stack}) => {
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const {nightModeEnabled, graphTheming} = useTheme();
|
const {nightModeEnabled, graphTheming} = useTheme();
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ const JoinAddressGraph = ({id, data, colors}) => {
|
|||||||
|
|
||||||
const valuesByAddress = {};
|
const valuesByAddress = {};
|
||||||
const dates = []
|
const dates = []
|
||||||
for (const point of data) {
|
for (const point of data || []) {
|
||||||
dates.push(point.date);
|
dates.push(point.date);
|
||||||
for (const address of point.joinAddresses) {
|
for (const address of point.joinAddresses) {
|
||||||
if (!valuesByAddress[address.joinAddress]) valuesByAddress[address.joinAddress] = [];
|
if (!valuesByAddress[address.joinAddress]) valuesByAddress[address.joinAddress] = [];
|
||||||
@ -61,7 +61,7 @@ const JoinAddressGraph = ({id, data, colors}) => {
|
|||||||
title: {text: ''},
|
title: {text: ''},
|
||||||
plotOptions: {
|
plotOptions: {
|
||||||
column: {
|
column: {
|
||||||
stacking: 'normal',
|
stacking: stack ? 'normal' : undefined,
|
||||||
lineWidth: 1
|
lineWidth: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -70,7 +70,7 @@ const JoinAddressGraph = ({id, data, colors}) => {
|
|||||||
},
|
},
|
||||||
series: series
|
series: series
|
||||||
})
|
})
|
||||||
}, [data, colors, graphTheming, id, t, nightModeEnabled])
|
}, [data, colors, graphTheming, id, t, nightModeEnabled, stack])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="chart-area" style={{height: "450px"}} id={id}>
|
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {useTheme} from "../../hooks/themeHook";
|
import {useTheme} from "../../hooks/themeHook";
|
||||||
import {withReducedSaturation} from "../../util/colors";
|
import {withReducedSaturation} from "../../util/colors";
|
||||||
|
import Scrollable from "../Scrollable";
|
||||||
|
|
||||||
const GroupRow = ({group, color}) => {
|
const GroupRow = ({group, color}) => {
|
||||||
return (
|
return (
|
||||||
@ -24,20 +25,22 @@ const GroupTable = ({groups, colors}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className={"table mb-0" + (nightModeEnabled ? " table-dark" : '')}>
|
<Scrollable>
|
||||||
<tbody>
|
<table className={"table mb-0" + (nightModeEnabled ? " table-dark" : '')}>
|
||||||
{groups.length ? groups.map((group, i) =>
|
<tbody>
|
||||||
<GroupRow key={i}
|
{groups.length ? groups.map((group, i) =>
|
||||||
group={group}
|
<GroupRow key={i}
|
||||||
color={getColor(i)}/>) :
|
group={group}
|
||||||
<tr>
|
color={getColor(i)}/>) :
|
||||||
<td>{t('generic.noData')}</td>
|
<tr>
|
||||||
<td>-</td>
|
<td>{t('generic.noData')}</td>
|
||||||
<td>-</td>
|
<td>-</td>
|
||||||
<td>-</td>
|
<td>-</td>
|
||||||
</tr>}
|
<td>-</td>
|
||||||
</tbody>
|
</tr>}
|
||||||
</table>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Scrollable>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ export const useDataRequest = (fetchMethod, parameters) => {
|
|||||||
console.warn(error);
|
console.warn(error);
|
||||||
datastore.finishUpdate(fetchMethod)
|
datastore.finishUpdate(fetchMethod)
|
||||||
setLoadingError(error);
|
setLoadingError(error);
|
||||||
finishUpdate(new Date().getTime(), "Error: " + error, datastore.isSomethingUpdating());
|
finishUpdate(0, "Error: " + error.message, datastore.isSomethingUpdating());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -117,11 +117,13 @@ export const fetchPingGraph = async (timestamp, identifier) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const fetchJoinAddressPie = async (timestamp, identifier) => {
|
export const fetchJoinAddressPie = async (timestamp, identifier) => {
|
||||||
const url = `/v1/graph?type=joinAddressPie&server=${identifier}×tamp=${timestamp}`;
|
const url = identifier ? `/v1/graph?type=joinAddressPie&server=${identifier}×tamp=${timestamp}` :
|
||||||
|
`/v1/graph?type=joinAddressPie×tamp=${timestamp}`;
|
||||||
return doGetRequest(url);
|
return doGetRequest(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchJoinAddressByDay = async (timestamp, identifier) => {
|
export const fetchJoinAddressByDay = async (timestamp, identifier) => {
|
||||||
const url = `/v1/graph?type=joinAddressByDay&server=${identifier}×tamp=${timestamp}`;
|
const url = identifier ? `/v1/graph?type=joinAddressByDay&server=${identifier}×tamp=${timestamp}` :
|
||||||
|
`/v1/graph?type=joinAddressByDay×tamp=${timestamp}`;
|
||||||
return doGetRequest(url);
|
return doGetRequest(url);
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
faCubes,
|
faCubes,
|
||||||
faGlobe,
|
faGlobe,
|
||||||
faInfoCircle,
|
faInfoCircle,
|
||||||
|
faLocationArrow,
|
||||||
faNetworkWired,
|
faNetworkWired,
|
||||||
faSearch,
|
faSearch,
|
||||||
faServer,
|
faServer,
|
||||||
@ -73,6 +74,7 @@ const NetworkSidebar = () => {
|
|||||||
icon: faChartLine,
|
icon: faChartLine,
|
||||||
href: "playerbase"
|
href: "playerbase"
|
||||||
},
|
},
|
||||||
|
{name: 'html.label.joinAddresses', icon: faLocationArrow, href: "join-addresses"},
|
||||||
// {name: 'html.label.playerRetention', icon: faUsersViewfinder, href: "retention"},
|
// {name: 'html.label.playerRetention', icon: faUsersViewfinder, href: "retention"},
|
||||||
{name: 'html.label.playerList', icon: faUserGroup, href: "players"},
|
{name: 'html.label.playerList', icon: faUserGroup, href: "players"},
|
||||||
{name: 'html.label.geolocations', icon: faGlobe, href: "geolocations"},
|
{name: 'html.label.geolocations', icon: faGlobe, href: "geolocations"},
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Col, Row} from "react-bootstrap-v5";
|
||||||
|
import JoinAddressGroupCard from "../../components/cards/server/graphs/JoinAddressGroupCard";
|
||||||
|
import JoinAddressGraphCard from "../../components/cards/server/graphs/JoinAddressGraphCard";
|
||||||
|
|
||||||
|
const NetworkJoinAddresses = () => {
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col lg={8}>
|
||||||
|
<JoinAddressGraphCard identifier={undefined}/>
|
||||||
|
</Col>
|
||||||
|
<Col lg={4}>
|
||||||
|
<JoinAddressGroupCard identifier={undefined}/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NetworkJoinAddresses
|
@ -2,17 +2,17 @@ import React from 'react';
|
|||||||
import {Col, Row} from "react-bootstrap-v5";
|
import {Col, Row} from "react-bootstrap-v5";
|
||||||
import JoinAddressGroupCard from "../../components/cards/server/graphs/JoinAddressGroupCard";
|
import JoinAddressGroupCard from "../../components/cards/server/graphs/JoinAddressGroupCard";
|
||||||
import JoinAddressGraphCard from "../../components/cards/server/graphs/JoinAddressGraphCard";
|
import JoinAddressGraphCard from "../../components/cards/server/graphs/JoinAddressGraphCard";
|
||||||
|
import {useParams} from "react-router-dom";
|
||||||
|
|
||||||
const ServerJoinAddresses = () => {
|
const ServerJoinAddresses = () => {
|
||||||
|
const {identifier} = useParams();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
<Col lg={8}>
|
<Col lg={8}>
|
||||||
<JoinAddressGraphCard/>
|
<JoinAddressGraphCard identifier={identifier}/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col lg={4}>
|
<Col lg={4}>
|
||||||
<JoinAddressGroupCard/>
|
<JoinAddressGroupCard identifier={identifier}/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user