mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-02-09 08:51:44 +01:00
3268/redesign join address visualization (#3558)
- Join address pie removed - Join address group mechanism added - User can select multiple addresses for each group - User can rename each group to their liking - The groups are stored in preferences so that user doesn't need to add them back every time - Use the join address group mechanism for time series of Join Addresses - Use the join address group mechanism for Player Retention - Small improvement to retention graph: Show multiple labels - Small improvement to site clock: Can now hover to show actual date Affected issues: - Close #3268
This commit is contained in:
parent
b6f68936cf
commit
f40e1498c1
@ -16,6 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.djrapitops.plan.delivery.domain;
|
package com.djrapitops.plan.delivery.domain;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object that has a value tied to a date.
|
* Object that has a value tied to a date.
|
||||||
*
|
*
|
||||||
@ -39,4 +41,25 @@ public class DateObj<T> implements DateHolder {
|
|||||||
public T getValue() {
|
public T getValue() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
DateObj<?> dateObj = (DateObj<?>) o;
|
||||||
|
return getDate() == dateObj.getDate() && Objects.equals(getValue(), dateObj.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(getDate(), getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DateObj{" +
|
||||||
|
"date=" + date +
|
||||||
|
", value=" + value +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
@ -19,6 +19,9 @@ package com.djrapitops.plan.delivery.domain.auth;
|
|||||||
import com.djrapitops.plan.settings.locale.lang.Lang;
|
import com.djrapitops.plan.settings.locale.lang.Lang;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,7 +50,8 @@ public enum WebPermission implements Supplier<String>, Lang {
|
|||||||
PAGE_NETWORK_SESSIONS_LIST("See list of sessions"),
|
PAGE_NETWORK_SESSIONS_LIST("See list of sessions"),
|
||||||
PAGE_NETWORK_JOIN_ADDRESSES("See Join Addresses -tab"),
|
PAGE_NETWORK_JOIN_ADDRESSES("See Join Addresses -tab"),
|
||||||
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
|
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
|
||||||
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph"),
|
@Deprecated
|
||||||
|
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph", true),
|
||||||
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
|
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
|
||||||
PAGE_NETWORK_RETENTION("See Player Retention -tab"),
|
PAGE_NETWORK_RETENTION("See Player Retention -tab"),
|
||||||
PAGE_NETWORK_GEOLOCATIONS("See Geolocations tab"),
|
PAGE_NETWORK_GEOLOCATIONS("See Geolocations tab"),
|
||||||
@ -82,7 +86,8 @@ public enum WebPermission implements Supplier<String>, Lang {
|
|||||||
PAGE_SERVER_SESSIONS_LIST("See list of sessions"),
|
PAGE_SERVER_SESSIONS_LIST("See list of sessions"),
|
||||||
PAGE_SERVER_JOIN_ADDRESSES("See Join Addresses -tab"),
|
PAGE_SERVER_JOIN_ADDRESSES("See Join Addresses -tab"),
|
||||||
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
|
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
|
||||||
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph"),
|
@Deprecated
|
||||||
|
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph", true),
|
||||||
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
|
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
|
||||||
PAGE_SERVER_RETENTION("See Player Retention -tab"),
|
PAGE_SERVER_RETENTION("See Player Retention -tab"),
|
||||||
PAGE_SERVER_GEOLOCATIONS("See Geolocations tab"),
|
PAGE_SERVER_GEOLOCATIONS("See Geolocations tab"),
|
||||||
@ -156,4 +161,23 @@ public enum WebPermission implements Supplier<String>, Lang {
|
|||||||
public String getDefault() {
|
public String getDefault() {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static WebPermission[] nonDeprecatedValues() {
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(Predicate.not(WebPermission::isDeprecated))
|
||||||
|
.toArray(WebPermission[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<WebPermission> findByPermission(String permission) {
|
||||||
|
String name = StringUtils.upperCase(permission).replace('.', '_');
|
||||||
|
try {
|
||||||
|
return Optional.of(valueOf(name));
|
||||||
|
} catch (IllegalArgumentException noSuchEnum) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDeprecated(String permission) {
|
||||||
|
return findByPermission(permission).map(WebPermission::isDeprecated).orElse(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.domain.datatransfer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data returned by {@link com.djrapitops.plan.delivery.webserver.resolver.json.PlayerJoinAddressJSONResolver}.
|
||||||
|
*
|
||||||
|
* @author AuroraLS3
|
||||||
|
*/
|
||||||
|
public class PlayerJoinAddresses {
|
||||||
|
|
||||||
|
private final List<String> joinAddresses;
|
||||||
|
private final Map<UUID, String> joinAddressByPlayer;
|
||||||
|
|
||||||
|
public PlayerJoinAddresses(List<String> joinAddresses, Map<UUID, String> joinAddressByPlayer) {
|
||||||
|
this.joinAddresses = joinAddresses;
|
||||||
|
this.joinAddressByPlayer = joinAddressByPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getJoinAddresses() {
|
||||||
|
return joinAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<UUID, String> getJoinAddressByPlayer() {
|
||||||
|
return joinAddressByPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
PlayerJoinAddresses that = (PlayerJoinAddresses) o;
|
||||||
|
return Objects.equals(getJoinAddresses(), that.getJoinAddresses()) && Objects.equals(getJoinAddressByPlayer(), that.getJoinAddressByPlayer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(getJoinAddresses(), getJoinAddressByPlayer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PlayerJoinAddresses{" +
|
||||||
|
"joinAddresses=" + joinAddresses +
|
||||||
|
", joinAddressByPlayer=" + joinAddressByPlayer +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -121,7 +121,6 @@ public class NetworkPageExporter extends FileExporter {
|
|||||||
"graph?type=uniqueAndNew",
|
"graph?type=uniqueAndNew",
|
||||||
"graph?type=hourlyUniqueAndNew",
|
"graph?type=hourlyUniqueAndNew",
|
||||||
"graph?type=serverPie",
|
"graph?type=serverPie",
|
||||||
"graph?type=joinAddressPie",
|
|
||||||
"graph?type=joinAddressByDay",
|
"graph?type=joinAddressByDay",
|
||||||
"graph?type=activity",
|
"graph?type=activity",
|
||||||
"graph?type=geolocation",
|
"graph?type=geolocation",
|
||||||
|
@ -137,7 +137,6 @@ public class ServerPageExporter extends FileExporter {
|
|||||||
"graph?type=geolocation&server=" + serverUUID,
|
"graph?type=geolocation&server=" + serverUUID,
|
||||||
"graph?type=uniqueAndNew&server=" + serverUUID,
|
"graph?type=uniqueAndNew&server=" + serverUUID,
|
||||||
"graph?type=hourlyUniqueAndNew&server=" + serverUUID,
|
"graph?type=hourlyUniqueAndNew&server=" + serverUUID,
|
||||||
"graph?type=joinAddressPie&server=" + serverUUID,
|
|
||||||
"graph?type=joinAddressByDay&server=" + serverUUID,
|
"graph?type=joinAddressByDay&server=" + serverUUID,
|
||||||
"graph?type=serverCalendar&server=" + serverUUID,
|
"graph?type=serverCalendar&server=" + serverUUID,
|
||||||
"graph?type=punchCard&server=" + serverUUID,
|
"graph?type=punchCard&server=" + serverUUID,
|
||||||
|
@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery.rendering.json;
|
|||||||
|
|
||||||
import com.djrapitops.plan.delivery.domain.DateObj;
|
import com.djrapitops.plan.delivery.domain.DateObj;
|
||||||
import com.djrapitops.plan.delivery.domain.RetentionData;
|
import com.djrapitops.plan.delivery.domain.RetentionData;
|
||||||
|
import com.djrapitops.plan.delivery.domain.datatransfer.PlayerJoinAddresses;
|
||||||
import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;
|
import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;
|
||||||
import com.djrapitops.plan.delivery.domain.mutators.PlayerKillMutator;
|
import com.djrapitops.plan.delivery.domain.mutators.PlayerKillMutator;
|
||||||
import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator;
|
import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator;
|
||||||
@ -160,14 +161,25 @@ public class JSONFactory {
|
|||||||
return db.query(PlayerRetentionQueries.fetchRetentionData());
|
return db.query(PlayerRetentionQueries.fetchRetentionData());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<UUID, String> playerJoinAddresses(ServerUUID serverUUID) {
|
public PlayerJoinAddresses playerJoinAddresses(ServerUUID serverUUID, boolean includeByPlayerMap) {
|
||||||
Database db = dbSystem.getDatabase();
|
Database db = dbSystem.getDatabase();
|
||||||
return db.query(JoinAddressQueries.latestJoinAddressesOfPlayers(serverUUID));
|
if (includeByPlayerMap) {
|
||||||
|
Map<UUID, String> addresses = db.query(JoinAddressQueries.latestJoinAddressesOfPlayers(serverUUID));
|
||||||
|
return new PlayerJoinAddresses(
|
||||||
|
addresses.values().stream().distinct().sorted().collect(Collectors.toList()),
|
||||||
|
addresses
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return new PlayerJoinAddresses(db.query(JoinAddressQueries.uniqueJoinAddresses(serverUUID)), null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<UUID, String> playerJoinAddresses() {
|
public PlayerJoinAddresses playerJoinAddresses(boolean includeByPlayerMap) {
|
||||||
Database db = dbSystem.getDatabase();
|
Database db = dbSystem.getDatabase();
|
||||||
return db.query(JoinAddressQueries.latestJoinAddressesOfPlayers());
|
return new PlayerJoinAddresses(
|
||||||
|
db.query(JoinAddressQueries.uniqueJoinAddresses()),
|
||||||
|
includeByPlayerMap ? db.query(JoinAddressQueries.latestJoinAddressesOfPlayers()) : null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Map<String, Object>> serverSessionsAsJSONMap(ServerUUID serverUUID) {
|
public List<Map<String, Object>> serverSessionsAsJSONMap(ServerUUID serverUUID) {
|
||||||
|
@ -32,7 +32,6 @@ import com.djrapitops.plan.delivery.rendering.json.graphs.line.LineGraphFactory;
|
|||||||
import com.djrapitops.plan.delivery.rendering.json.graphs.line.PingGraph;
|
import com.djrapitops.plan.delivery.rendering.json.graphs.line.PingGraph;
|
||||||
import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point;
|
import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point;
|
||||||
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.Pie;
|
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.Pie;
|
||||||
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.PieSlice;
|
|
||||||
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie;
|
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie;
|
||||||
import com.djrapitops.plan.delivery.rendering.json.graphs.special.WorldMap;
|
import com.djrapitops.plan.delivery.rendering.json.graphs.special.WorldMap;
|
||||||
import com.djrapitops.plan.delivery.rendering.json.graphs.stack.StackGraph;
|
import com.djrapitops.plan.delivery.rendering.json.graphs.stack.StackGraph;
|
||||||
@ -57,7 +56,7 @@ import com.djrapitops.plan.storage.database.queries.analysis.PlayerCountQueries;
|
|||||||
import com.djrapitops.plan.storage.database.queries.objects.*;
|
import com.djrapitops.plan.storage.database.queries.objects.*;
|
||||||
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
|
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
|
||||||
import com.djrapitops.plan.utilities.comparators.DateHolderOldestComparator;
|
import com.djrapitops.plan.utilities.comparators.DateHolderOldestComparator;
|
||||||
import com.djrapitops.plan.utilities.comparators.PieSliceComparator;
|
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||||
import com.djrapitops.plan.utilities.java.Lists;
|
import com.djrapitops.plan.utilities.java.Lists;
|
||||||
import com.djrapitops.plan.utilities.java.Maps;
|
import com.djrapitops.plan.utilities.java.Maps;
|
||||||
import net.playeranalytics.plugin.scheduling.TimeAmount;
|
import net.playeranalytics.plugin.scheduling.TimeAmount;
|
||||||
@ -457,34 +456,6 @@ public class GraphJSONCreator {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> playerHostnamePieJSONAsMap() {
|
|
||||||
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
|
|
||||||
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses());
|
|
||||||
|
|
||||||
translateUnknown(joinAddresses);
|
|
||||||
|
|
||||||
List<PieSlice> slices = graphs.pie().joinAddressPie(joinAddresses).getSlices();
|
|
||||||
slices.sort(new PieSliceComparator());
|
|
||||||
return Maps.builder(String.class, Object.class)
|
|
||||||
.put("colors", pieColors)
|
|
||||||
.put("slices", slices)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> playerHostnamePieJSONAsMap(ServerUUID serverUUID) {
|
|
||||||
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
|
|
||||||
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses(serverUUID));
|
|
||||||
|
|
||||||
translateUnknown(joinAddresses);
|
|
||||||
|
|
||||||
List<PieSlice> slices = graphs.pie().joinAddressPie(joinAddresses).getSlices();
|
|
||||||
slices.sort(new PieSliceComparator());
|
|
||||||
return Maps.builder(String.class, Object.class)
|
|
||||||
.put("colors", pieColors)
|
|
||||||
.put("slices", slices)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void translateUnknown(Map<String, Integer> joinAddresses) {
|
public void translateUnknown(Map<String, Integer> joinAddresses) {
|
||||||
Integer unknown = joinAddresses.get(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
Integer unknown = joinAddresses.get(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||||
if (unknown != null) {
|
if (unknown != null) {
|
||||||
@ -493,16 +464,16 @@ public class GraphJSONCreator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> joinAddressesByDay(ServerUUID serverUUID, long after, long before) {
|
public Map<String, Object> joinAddressesByDay(ServerUUID serverUUID, long after, long before, @Untrusted List<String> addressFilter) {
|
||||||
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, addressFilter));
|
||||||
|
|
||||||
return mapToJson(pieColors, joinAddresses);
|
return mapToJson(pieColors, joinAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> joinAddressesByDay(long after, long before) {
|
public Map<String, Object> joinAddressesByDay(long after, long before, @Untrusted List<String> addressFilter) {
|
||||||
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(config.getTimeZone().getOffset(System.currentTimeMillis()), after, before));
|
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(config.getTimeZone().getOffset(System.currentTimeMillis()), after, before, addressFilter));
|
||||||
|
|
||||||
return mapToJson(pieColors, joinAddresses);
|
return mapToJson(pieColors, joinAddresses);
|
||||||
}
|
}
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of Player Analytics (Plan).
|
|
||||||
*
|
|
||||||
* Plan is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Plan is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package com.djrapitops.plan.delivery.rendering.json.graphs.pie;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class JoinAddressPie extends Pie {
|
|
||||||
|
|
||||||
JoinAddressPie(Map<String, Integer> joinAddresses) {
|
|
||||||
super(turnToSlices(joinAddresses));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<PieSlice> turnToSlices(Map<String, Integer> joinAddresses) {
|
|
||||||
List<PieSlice> slices = new ArrayList<>();
|
|
||||||
for (Map.Entry<String, Integer> address : joinAddresses.entrySet()) {
|
|
||||||
String joinAddress = address.getKey();
|
|
||||||
Integer total = address.getValue();
|
|
||||||
slices.add(new PieSlice(joinAddress, total));
|
|
||||||
}
|
|
||||||
return slices;
|
|
||||||
}
|
|
||||||
}
|
|
@ -68,10 +68,6 @@ public class PieGraphFactory {
|
|||||||
return new ServerPreferencePie(playtimeByServerName);
|
return new ServerPreferencePie(playtimeByServerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pie joinAddressPie(Map<String, Integer> joinAddresses) {
|
|
||||||
return new JoinAddressPie(joinAddresses);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WorldPie worldPie(WorldTimes worldTimes) {
|
public WorldPie worldPie(WorldTimes worldTimes) {
|
||||||
WorldAliasSettings worldAliasSettings = config.getWorldAliasSettings();
|
WorldAliasSettings worldAliasSettings = config.getWorldAliasSettings();
|
||||||
Map<String, Long> playtimePerAlias = worldAliasSettings.getPlaytimePerAlias(worldTimes);
|
Map<String, Long> playtimePerAlias = worldAliasSettings.getPlaytimePerAlias(worldTimes);
|
||||||
|
@ -42,7 +42,6 @@ public enum DataID {
|
|||||||
GRAPH_ACTIVITY,
|
GRAPH_ACTIVITY,
|
||||||
GRAPH_PING,
|
GRAPH_PING,
|
||||||
GRAPH_SERVER_PIE,
|
GRAPH_SERVER_PIE,
|
||||||
GRAPH_HOSTNAME_PIE,
|
|
||||||
GRAPH_PUNCHCARD,
|
GRAPH_PUNCHCARD,
|
||||||
SERVER_OVERVIEW,
|
SERVER_OVERVIEW,
|
||||||
ONLINE_OVERVIEW,
|
ONLINE_OVERVIEW,
|
||||||
@ -54,12 +53,26 @@ public enum DataID {
|
|||||||
EXTENSION_TABS,
|
EXTENSION_TABS,
|
||||||
EXTENSION_JSON,
|
EXTENSION_JSON,
|
||||||
LIST_SERVERS,
|
LIST_SERVERS,
|
||||||
JOIN_ADDRESSES_BY_DAY,
|
JOIN_ADDRESSES_BY_DAY(false),
|
||||||
PLAYER_RETENTION,
|
PLAYER_RETENTION,
|
||||||
PLAYER_JOIN_ADDRESSES,
|
PLAYER_JOIN_ADDRESSES,
|
||||||
PLAYER_ALLOWLIST_BOUNCES,
|
PLAYER_ALLOWLIST_BOUNCES,
|
||||||
;
|
;
|
||||||
|
|
||||||
|
private final boolean cacheable;
|
||||||
|
|
||||||
|
DataID() {
|
||||||
|
this(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
DataID(boolean cacheable) {
|
||||||
|
this.cacheable = cacheable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCacheable() {
|
||||||
|
return cacheable;
|
||||||
|
}
|
||||||
|
|
||||||
public String of(ServerUUID serverUUID) {
|
public String of(ServerUUID serverUUID) {
|
||||||
if (serverUUID == null) return name();
|
if (serverUUID == null) return name();
|
||||||
return name() + "_" + serverUUID;
|
return name() + "_" + serverUUID;
|
||||||
|
@ -74,6 +74,10 @@ public interface JSONStorage extends SubSystem {
|
|||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static StoredJSON fromObject(Object json, long timestamp) {
|
||||||
|
return new StoredJSON(new Gson().toJson(json), timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
public String getJson() {
|
public String getJson() {
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
@ -39,11 +39,16 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
|||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
import jakarta.ws.rs.Path;
|
import jakarta.ws.rs.Path;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves /v1/graph JSON requests.
|
* Resolves /v1/graph JSON requests.
|
||||||
@ -117,7 +122,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||||||
@ExampleObject("aggregatedPing"),
|
@ExampleObject("aggregatedPing"),
|
||||||
@ExampleObject("punchCard"),
|
@ExampleObject("punchCard"),
|
||||||
@ExampleObject("serverPie"),
|
@ExampleObject("serverPie"),
|
||||||
@ExampleObject("joinAddressPie"),
|
|
||||||
@ExampleObject("joinAddressByDay"),
|
@ExampleObject("joinAddressByDay"),
|
||||||
}),
|
}),
|
||||||
@Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for", examples = {
|
@Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for", examples = {
|
||||||
@ -156,15 +160,22 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||||||
JSONStorage.StoredJSON storedJSON;
|
JSONStorage.StoredJSON storedJSON;
|
||||||
if (request.getQuery().get("server").isPresent()) {
|
if (request.getQuery().get("server").isPresent()) {
|
||||||
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
|
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
|
||||||
storedJSON = jsonResolverService.resolve(
|
Function<ServerUUID, Object> generationFunction = theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery());
|
||||||
timestamp, dataID, serverUUID,
|
if (dataID.isCacheable()) {
|
||||||
theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery())
|
storedJSON = jsonResolverService.resolve(timestamp, dataID, serverUUID, generationFunction);
|
||||||
);
|
} else {
|
||||||
|
storedJSON = JSONStorage.StoredJSON.fromObject(generationFunction.apply(serverUUID), System.currentTimeMillis());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Assume network
|
// Assume network
|
||||||
storedJSON = jsonResolverService.resolve(
|
Supplier<Object> generationFunction = () -> generateGraphDataJSONOfType(dataID, request.getQuery());
|
||||||
timestamp, dataID, () -> generateGraphDataJSONOfType(dataID, request.getQuery())
|
if (dataID.isCacheable()) {
|
||||||
);
|
storedJSON = jsonResolverService.resolve(
|
||||||
|
timestamp, dataID, generationFunction
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
storedJSON = JSONStorage.StoredJSON.fromObject(generationFunction.get(), System.currentTimeMillis());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return storedJSON;
|
return storedJSON;
|
||||||
}
|
}
|
||||||
@ -197,8 +208,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||||||
return DataID.GRAPH_PUNCHCARD;
|
return DataID.GRAPH_PUNCHCARD;
|
||||||
case "serverPie":
|
case "serverPie":
|
||||||
return DataID.GRAPH_SERVER_PIE;
|
return DataID.GRAPH_SERVER_PIE;
|
||||||
case "joinAddressPie":
|
|
||||||
return DataID.GRAPH_HOSTNAME_PIE;
|
|
||||||
case "joinAddressByDay":
|
case "joinAddressByDay":
|
||||||
return DataID.JOIN_ADDRESSES_BY_DAY;
|
return DataID.JOIN_ADDRESSES_BY_DAY;
|
||||||
default:
|
default:
|
||||||
@ -229,8 +238,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||||||
return List.of(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS);
|
return List.of(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS);
|
||||||
case GRAPH_WORLD_MAP:
|
case GRAPH_WORLD_MAP:
|
||||||
return List.of(WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP);
|
return List.of(WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP);
|
||||||
case GRAPH_HOSTNAME_PIE:
|
|
||||||
return List.of(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE);
|
|
||||||
case JOIN_ADDRESSES_BY_DAY:
|
case JOIN_ADDRESSES_BY_DAY:
|
||||||
return List.of(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME);
|
return List.of(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME);
|
||||||
default:
|
default:
|
||||||
@ -258,8 +265,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||||||
return List.of(WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP);
|
return List.of(WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP);
|
||||||
case GRAPH_ONLINE_PROXIES:
|
case GRAPH_ONLINE_PROXIES:
|
||||||
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_ONLINE);
|
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_ONLINE);
|
||||||
case GRAPH_HOSTNAME_PIE:
|
|
||||||
return List.of(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE);
|
|
||||||
case JOIN_ADDRESSES_BY_DAY:
|
case JOIN_ADDRESSES_BY_DAY:
|
||||||
return List.of(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME);
|
return List.of(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME);
|
||||||
default:
|
default:
|
||||||
@ -283,8 +288,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||||||
return graphJSON.serverCalendarJSON(serverUUID);
|
return graphJSON.serverCalendarJSON(serverUUID);
|
||||||
case GRAPH_WORLD_PIE:
|
case GRAPH_WORLD_PIE:
|
||||||
return graphJSON.serverWorldPieJSONAsMap(serverUUID);
|
return graphJSON.serverWorldPieJSONAsMap(serverUUID);
|
||||||
case GRAPH_HOSTNAME_PIE:
|
|
||||||
return graphJSON.playerHostnamePieJSONAsMap(serverUUID);
|
|
||||||
case GRAPH_ACTIVITY:
|
case GRAPH_ACTIVITY:
|
||||||
return graphJSON.activityGraphsJSONAsMap(serverUUID);
|
return graphJSON.activityGraphsJSONAsMap(serverUUID);
|
||||||
case GRAPH_WORLD_MAP:
|
case GRAPH_WORLD_MAP:
|
||||||
@ -294,19 +297,24 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||||||
case GRAPH_PUNCHCARD:
|
case GRAPH_PUNCHCARD:
|
||||||
return graphJSON.punchCardJSONAsMap(serverUUID);
|
return graphJSON.punchCardJSONAsMap(serverUUID);
|
||||||
case JOIN_ADDRESSES_BY_DAY:
|
case JOIN_ADDRESSES_BY_DAY:
|
||||||
try {
|
return joinAddressGraph(serverUUID, query);
|
||||||
return graphJSON.joinAddressesByDay(serverUUID,
|
|
||||||
query.get("after").map(Long::parseLong).orElse(0L),
|
|
||||||
query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis())
|
|
||||||
);
|
|
||||||
} catch (@Untrusted NumberFormatException e) {
|
|
||||||
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
throw new BadRequestException("Graph type not supported with server-parameter (" + id.name() + ")");
|
throw new BadRequestException("Graph type not supported with server-parameter (" + id.name() + ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> joinAddressGraph(ServerUUID serverUUID, @Untrusted URIQuery query) {
|
||||||
|
try {
|
||||||
|
Long after = query.get("after").map(Long::parseLong).orElse(0L);
|
||||||
|
Long before = query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis());
|
||||||
|
@Untrusted List<String> addressFilter = query.get("addresses").map(s -> StringUtils.split(s, ','))
|
||||||
|
.map(Arrays::asList).orElse(List.of());
|
||||||
|
return graphJSON.joinAddressesByDay(serverUUID, after, before, addressFilter);
|
||||||
|
} catch (@Untrusted NumberFormatException e) {
|
||||||
|
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Object generateGraphDataJSONOfType(DataID id, @Untrusted URIQuery query) {
|
private Object generateGraphDataJSONOfType(DataID id, @Untrusted URIQuery query) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case GRAPH_ACTIVITY:
|
case GRAPH_ACTIVITY:
|
||||||
@ -319,23 +327,26 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||||||
return graphJSON.networkCalendarJSON();
|
return graphJSON.networkCalendarJSON();
|
||||||
case GRAPH_SERVER_PIE:
|
case GRAPH_SERVER_PIE:
|
||||||
return graphJSON.serverPreferencePieJSONAsMap();
|
return graphJSON.serverPreferencePieJSONAsMap();
|
||||||
case GRAPH_HOSTNAME_PIE:
|
|
||||||
return graphJSON.playerHostnamePieJSONAsMap();
|
|
||||||
case GRAPH_WORLD_MAP:
|
case GRAPH_WORLD_MAP:
|
||||||
return graphJSON.geolocationGraphsJSONAsMap();
|
return graphJSON.geolocationGraphsJSONAsMap();
|
||||||
case GRAPH_ONLINE_PROXIES:
|
case GRAPH_ONLINE_PROXIES:
|
||||||
return graphJSON.proxyPlayersOnlineGraphs();
|
return graphJSON.proxyPlayersOnlineGraphs();
|
||||||
case JOIN_ADDRESSES_BY_DAY:
|
case JOIN_ADDRESSES_BY_DAY:
|
||||||
try {
|
return joinAddressGraph(query);
|
||||||
return graphJSON.joinAddressesByDay(
|
|
||||||
query.get("after").map(Long::parseLong).orElse(0L),
|
|
||||||
query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis())
|
|
||||||
);
|
|
||||||
} catch (@Untrusted NumberFormatException e) {
|
|
||||||
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
throw new BadRequestException("Graph type not supported without server-parameter (" + id.name() + ")");
|
throw new BadRequestException("Graph type not supported without server-parameter (" + id.name() + ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> joinAddressGraph(URIQuery query) {
|
||||||
|
try {
|
||||||
|
Long after = query.get("after").map(Long::parseLong).orElse(0L);
|
||||||
|
Long before = query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis());
|
||||||
|
@Untrusted List<String> addressFilter = query.get("addresses").map(s -> StringUtils.split(s, ','))
|
||||||
|
.map(Arrays::asList).orElse(List.of());
|
||||||
|
return graphJSON.joinAddressesByDay(after, before, addressFilter);
|
||||||
|
} catch (@Untrusted NumberFormatException e) {
|
||||||
|
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -17,11 +17,13 @@
|
|||||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||||
|
|
||||||
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
|
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
|
||||||
|
import com.djrapitops.plan.delivery.domain.datatransfer.PlayerJoinAddresses;
|
||||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||||
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
|
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||||
import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
|
import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
|
||||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||||
@ -34,6 +36,7 @@ import io.swagger.v3.oas.annotations.Parameter;
|
|||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
@ -42,7 +45,6 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,17 +71,27 @@ public class PlayerJoinAddressJSONResolver extends JSONResolver {
|
|||||||
@Override
|
@Override
|
||||||
public boolean canAccess(@Untrusted Request request) {
|
public boolean canAccess(@Untrusted Request request) {
|
||||||
WebUser user = request.getUser().orElse(new WebUser(""));
|
WebUser user = request.getUser().orElse(new WebUser(""));
|
||||||
if (request.getQuery().get("server").isPresent()) {
|
@Untrusted URIQuery query = request.getQuery();
|
||||||
return user.hasPermission(WebPermission.PAGE_SERVER_RETENTION);
|
Optional<String> listOnly = query.get("listOnly");
|
||||||
|
if (query.get("server").isPresent()) {
|
||||||
|
if (listOnly.isEmpty()) {
|
||||||
|
return user.hasPermission(WebPermission.PAGE_SERVER_RETENTION);
|
||||||
|
} else {
|
||||||
|
return user.hasPermission(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (listOnly.isEmpty()) {
|
||||||
|
return user.hasPermission(WebPermission.PAGE_NETWORK_RETENTION);
|
||||||
|
} else {
|
||||||
|
return user.hasPermission(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME);
|
||||||
}
|
}
|
||||||
return user.hasPermission(WebPermission.PAGE_NETWORK_RETENTION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Operation(
|
@Operation(
|
||||||
description = "Get join address information of players for server or network",
|
description = "Get join address information of players for server or network",
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)),
|
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON, schema = @Schema(implementation = PlayerJoinAddresses.class))),
|
||||||
@ApiResponse(responseCode = "400", description = "If 'server' parameter is not an existing server")
|
@ApiResponse(responseCode = "400", description = "If 'server' parameter is not an existing server")
|
||||||
},
|
},
|
||||||
parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = {
|
parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = {
|
||||||
@ -105,12 +117,12 @@ public class PlayerJoinAddressJSONResolver extends JSONResolver {
|
|||||||
if (request.getQuery().get("server").isPresent()) {
|
if (request.getQuery().get("server").isPresent()) {
|
||||||
ServerUUID serverUUID = identifiers.getServerUUID(request);
|
ServerUUID serverUUID = identifiers.getServerUUID(request);
|
||||||
return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES, serverUUID,
|
return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES, serverUUID,
|
||||||
theUUID -> Collections.singletonMap("join_address_by_player", jsonFactory.playerJoinAddresses(theUUID))
|
serverUUID1 -> jsonFactory.playerJoinAddresses(serverUUID1, request.getQuery().get("listOnly").isEmpty())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Assume network
|
// Assume network
|
||||||
return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES,
|
return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES,
|
||||||
() -> Collections.singletonMap("join_address_by_player", jsonFactory.playerJoinAddresses())
|
() -> jsonFactory.playerJoinAddresses(request.getQuery().get("listOnly").isEmpty())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ import javax.inject.Inject;
|
|||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Endpoint for getting list of available Plan web permissions.
|
* Endpoint for getting list of available Plan web permissions.
|
||||||
@ -75,7 +77,10 @@ public class WebPermissionJSONResolver implements Resolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Response getResponse() {
|
private Response getResponse() {
|
||||||
List<String> permissions = dbSystem.getDatabase().query(WebUserQueries.fetchAvailablePermissions());
|
List<String> permissions = dbSystem.getDatabase().query(WebUserQueries.fetchAvailablePermissions())
|
||||||
|
.stream()
|
||||||
|
.filter(Predicate.not(WebPermission::isDeprecated))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
WebPermissionList permissionList = new WebPermissionList(permissions);
|
WebPermissionList permissionList = new WebPermissionList(permissions);
|
||||||
return Response.builder()
|
return Response.builder()
|
||||||
|
@ -108,7 +108,7 @@ public class LocaleSystem implements SubSystem {
|
|||||||
HtmlLang.values(),
|
HtmlLang.values(),
|
||||||
JSLang.values(),
|
JSLang.values(),
|
||||||
PluginLang.values(),
|
PluginLang.values(),
|
||||||
WebPermission.values(),
|
WebPermission.nonDeprecatedValues(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,6 +219,8 @@ public enum HtmlLang implements Lang {
|
|||||||
LABEL_TITLE_SERVER_CALENDAR("html.label.serverCalendar", "Server Calendar"),
|
LABEL_TITLE_SERVER_CALENDAR("html.label.serverCalendar", "Server Calendar"),
|
||||||
LABEL_TITLE_NETWORK_CALENDAR("html.label.networkCalendar", "Network Calendar"),
|
LABEL_TITLE_NETWORK_CALENDAR("html.label.networkCalendar", "Network Calendar"),
|
||||||
LABEL_LABEL_JOIN_ADDRESS("html.label.joinAddress", "Join Address"),
|
LABEL_LABEL_JOIN_ADDRESS("html.label.joinAddress", "Join Address"),
|
||||||
|
LABEL_ADD_JOIN_ADDRESS_GROUP("html.label.addJoinAddressGroup", "Add address group"),
|
||||||
|
LABEL_ADDRESS_GROUP("html.label.addressGroup", "Address group {{n}}"),
|
||||||
LABEL_LABEL_SESSION_MEDIAN("html.label.medianSessionLength", "Median Session Length"),
|
LABEL_LABEL_SESSION_MEDIAN("html.label.medianSessionLength", "Median Session Length"),
|
||||||
LABEL_LABEL_KDR("html.label.kdr", "KDR"),
|
LABEL_LABEL_KDR("html.label.kdr", "KDR"),
|
||||||
LABEL_TITLE_INSIGHTS("html.label.insights", "Insights"),
|
LABEL_TITLE_INSIGHTS("html.label.insights", "Insights"),
|
||||||
|
@ -28,6 +28,8 @@ import com.djrapitops.plan.storage.database.sql.tables.ServerTable;
|
|||||||
import com.djrapitops.plan.storage.database.sql.tables.SessionsTable;
|
import com.djrapitops.plan.storage.database.sql.tables.SessionsTable;
|
||||||
import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
|
import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
|
||||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||||
|
import org.apache.commons.text.TextStringBuilder;
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
@ -43,6 +45,7 @@ public class JoinAddressQueries {
|
|||||||
/* Static method class */
|
/* Static method class */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
public static Query<Map<String, Integer>> latestJoinAddresses() {
|
public static Query<Map<String, Integer>> latestJoinAddresses() {
|
||||||
String selectLatestJoinAddresses = SELECT +
|
String selectLatestJoinAddresses = SELECT +
|
||||||
"COUNT(1) as total," +
|
"COUNT(1) as total," +
|
||||||
@ -66,6 +69,7 @@ public class JoinAddressQueries {
|
|||||||
joinAddresses.put(UUID.fromString(set.getString(UsersTable.USER_UUID)), set.getString(JoinAddressTable.JOIN_ADDRESS));
|
joinAddresses.put(UUID.fromString(set.getString(UsersTable.USER_UUID)), set.getString(JoinAddressTable.JOIN_ADDRESS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
public static Query<Map<String, Integer>> latestJoinAddresses(ServerUUID serverUUID) {
|
public static Query<Map<String, Integer>> latestJoinAddresses(ServerUUID serverUUID) {
|
||||||
String selectLatestSessionStarts = SELECT + SessionsTable.USER_ID + ",MAX(" + SessionsTable.SESSION_START + ") as max_start" +
|
String selectLatestSessionStarts = SELECT + SessionsTable.USER_ID + ",MAX(" + SessionsTable.SESSION_START + ") as max_start" +
|
||||||
FROM + SessionsTable.TABLE_NAME + " max_s" +
|
FROM + SessionsTable.TABLE_NAME + " max_s" +
|
||||||
@ -141,6 +145,28 @@ public class JoinAddressQueries {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static QueryStatement<List<String>> allJoinAddresses(ServerUUID serverUUID) {
|
||||||
|
String sql = SELECT + DISTINCT + JoinAddressTable.JOIN_ADDRESS +
|
||||||
|
FROM + JoinAddressTable.TABLE_NAME + " j" +
|
||||||
|
INNER_JOIN + SessionsTable.TABLE_NAME + " s ON s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
|
||||||
|
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
||||||
|
ORDER_BY + JoinAddressTable.JOIN_ADDRESS + " ASC";
|
||||||
|
|
||||||
|
return new QueryStatement<>(sql, 100) {
|
||||||
|
@Override
|
||||||
|
public void prepare(PreparedStatement statement) throws SQLException {
|
||||||
|
statement.setString(1, serverUUID.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> processResults(ResultSet set) throws SQLException {
|
||||||
|
List<String> joinAddresses = new ArrayList<>();
|
||||||
|
while (set.next()) joinAddresses.add(set.getString(JoinAddressTable.JOIN_ADDRESS));
|
||||||
|
return joinAddresses;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static Query<List<String>> uniqueJoinAddresses() {
|
public static Query<List<String>> uniqueJoinAddresses() {
|
||||||
return db -> {
|
return db -> {
|
||||||
List<String> addresses = db.query(allJoinAddresses());
|
List<String> addresses = db.query(allJoinAddresses());
|
||||||
@ -151,6 +177,16 @@ public class JoinAddressQueries {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Query<List<String>> uniqueJoinAddresses(ServerUUID serverUUID) {
|
||||||
|
return db -> {
|
||||||
|
List<String> addresses = db.query(allJoinAddresses(serverUUID));
|
||||||
|
if (!addresses.contains(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)) {
|
||||||
|
addresses.add(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||||
|
}
|
||||||
|
return addresses;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static Query<Set<Integer>> userIdsOfPlayersWithJoinAddresses(@Untrusted List<String> joinAddresses) {
|
public static Query<Set<Integer>> userIdsOfPlayersWithJoinAddresses(@Untrusted List<String> joinAddresses) {
|
||||||
String sql = SELECT + DISTINCT + SessionsTable.USER_ID +
|
String sql = SELECT + DISTINCT + SessionsTable.USER_ID +
|
||||||
FROM + JoinAddressTable.TABLE_NAME + " j" +
|
FROM + JoinAddressTable.TABLE_NAME + " j" +
|
||||||
@ -162,21 +198,27 @@ public class JoinAddressQueries {
|
|||||||
return db -> db.querySet(sql, RowExtractors.getInt(SessionsTable.USER_ID), joinAddresses.toArray());
|
return db -> db.querySet(sql, RowExtractors.getInt(SessionsTable.USER_ID), joinAddresses.toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(ServerUUID serverUUID, long timezoneOffset, long after, long before) {
|
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(ServerUUID serverUUID, long timezoneOffset, long after, long before, @Untrusted List<String> addressFilter) {
|
||||||
return db -> {
|
return db -> {
|
||||||
Sql sql = db.getSql();
|
Sql sql = db.getSql();
|
||||||
|
|
||||||
|
List<Integer> ids = db.query(joinAddressIds(addressFilter));
|
||||||
|
if (ids != null && ids.isEmpty()) return List.of();
|
||||||
|
|
||||||
String selectAddresses = SELECT +
|
String selectAddresses = SELECT +
|
||||||
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
|
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
|
||||||
"*1000 as date," +
|
"*1000 as date," +
|
||||||
JoinAddressTable.JOIN_ADDRESS +
|
JoinAddressTable.JOIN_ADDRESS + ',' +
|
||||||
|
SessionsTable.USER_ID +
|
||||||
", COUNT(1) as count" +
|
", COUNT(1) as count" +
|
||||||
FROM + SessionsTable.TABLE_NAME + " s" +
|
FROM + SessionsTable.TABLE_NAME + " s" +
|
||||||
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
|
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
|
||||||
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
||||||
AND + SessionsTable.SESSION_START + ">?" +
|
AND + SessionsTable.SESSION_START + ">?" +
|
||||||
AND + SessionsTable.SESSION_START + "<=?" +
|
AND + SessionsTable.SESSION_START + "<=?" +
|
||||||
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS;
|
(ids == null ? "" : AND + "j." + JoinAddressTable.ID +
|
||||||
|
" IN (" + new TextStringBuilder().appendWithSeparators(ids, ",").build() + ")") +
|
||||||
|
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS + ',' + SessionsTable.USER_ID;
|
||||||
|
|
||||||
return db.query(new QueryStatement<>(selectAddresses, 1000) {
|
return db.query(new QueryStatement<>(selectAddresses, 1000) {
|
||||||
@Override
|
@Override
|
||||||
@ -193,9 +235,9 @@ public class JoinAddressQueries {
|
|||||||
while (set.next()) {
|
while (set.next()) {
|
||||||
long date = set.getLong("date");
|
long date = set.getLong("date");
|
||||||
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
|
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
|
||||||
int count = set.getInt("count");
|
|
||||||
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
|
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
|
||||||
joinAddresses.put(joinAddress, count);
|
// We ignore the count and get the number of players instead of sessions
|
||||||
|
joinAddresses.compute(joinAddress, (key, oldValue) -> oldValue != null ? oldValue + 1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return addressesByDate.entrySet()
|
return addressesByDate.entrySet()
|
||||||
@ -206,20 +248,37 @@ public class JoinAddressQueries {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(long timezoneOffset, long after, long before) {
|
public static Query<List<Integer>> joinAddressIds(@Untrusted List<String> addresses) {
|
||||||
|
return db -> {
|
||||||
|
if (addresses.isEmpty()) return null;
|
||||||
|
|
||||||
|
String selectJoinAddressIds = SELECT + JoinAddressTable.ID +
|
||||||
|
FROM + JoinAddressTable.TABLE_NAME +
|
||||||
|
WHERE + JoinAddressTable.JOIN_ADDRESS + " IN (" + Sql.nParameters(addresses.size()) + ")";
|
||||||
|
return db.queryList(selectJoinAddressIds, set -> set.getInt(JoinAddressTable.ID), addresses);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(long timezoneOffset, long after, long before, @Untrusted List<String> addressFilter) {
|
||||||
return db -> {
|
return db -> {
|
||||||
Sql sql = db.getSql();
|
Sql sql = db.getSql();
|
||||||
|
|
||||||
|
List<Integer> ids = db.query(joinAddressIds(addressFilter));
|
||||||
|
if (ids != null && ids.isEmpty()) return List.of();
|
||||||
|
|
||||||
String selectAddresses = SELECT +
|
String selectAddresses = SELECT +
|
||||||
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
|
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
|
||||||
"*1000 as date," +
|
"*1000 as date," +
|
||||||
JoinAddressTable.JOIN_ADDRESS +
|
JoinAddressTable.JOIN_ADDRESS + ',' +
|
||||||
|
SessionsTable.USER_ID +
|
||||||
", COUNT(1) as count" +
|
", COUNT(1) as count" +
|
||||||
FROM + SessionsTable.TABLE_NAME + " s" +
|
FROM + SessionsTable.TABLE_NAME + " s" +
|
||||||
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
|
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
|
||||||
WHERE + SessionsTable.SESSION_START + ">?" +
|
WHERE + SessionsTable.SESSION_START + ">?" +
|
||||||
AND + SessionsTable.SESSION_START + "<=?" +
|
AND + SessionsTable.SESSION_START + "<=?" +
|
||||||
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS;
|
(ids == null ? "" : AND + "j." + JoinAddressTable.ID +
|
||||||
|
" IN (" + new TextStringBuilder().appendWithSeparators(ids, ",").build() + ")") +
|
||||||
|
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS + ',' + SessionsTable.USER_ID;
|
||||||
|
|
||||||
return db.query(new QueryStatement<>(selectAddresses, 1000) {
|
return db.query(new QueryStatement<>(selectAddresses, 1000) {
|
||||||
@Override
|
@Override
|
||||||
@ -235,9 +294,9 @@ public class JoinAddressQueries {
|
|||||||
while (set.next()) {
|
while (set.next()) {
|
||||||
long date = set.getLong("date");
|
long date = set.getLong("date");
|
||||||
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
|
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
|
||||||
int count = set.getInt("count");
|
|
||||||
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
|
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
|
||||||
joinAddresses.put(joinAddress, count);
|
// We ignore the count and get the number of players instead of sessions
|
||||||
|
joinAddresses.compute(joinAddress, (key, oldValue) -> oldValue != null ? oldValue + 1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return addressesByDate.entrySet()
|
return addressesByDate.entrySet()
|
||||||
|
@ -39,7 +39,7 @@ public class UpdateWebPermissionsPatch extends Patch {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasBeenApplied() {
|
public boolean hasBeenApplied() {
|
||||||
List<String> defaultPermissions = Arrays.stream(WebPermission.values())
|
List<String> defaultPermissions = Arrays.stream(WebPermission.nonDeprecatedValues())
|
||||||
.map(WebPermission::getPermission)
|
.map(WebPermission::getPermission)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
List<String> storedPermissions = query(WebUserQueries.fetchAvailablePermissions());
|
List<String> storedPermissions = query(WebUserQueries.fetchAvailablePermissions());
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "活跃"
|
active: "活跃"
|
||||||
activePlaytime: "活跃时间"
|
activePlaytime: "活跃时间"
|
||||||
activityIndex: "活跃指数"
|
activityIndex: "活跃指数"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "挂机"
|
afk: "挂机"
|
||||||
afkTime: "挂机时间"
|
afkTime: "挂机时间"
|
||||||
all: "全部"
|
all: "全部"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "查看按国家划分的Ping表"
|
page_network_geolocations_ping_per_country: "查看按国家划分的Ping表"
|
||||||
page_network_join_addresses: "查看加入地址 - 选项卡"
|
page_network_join_addresses: "查看加入地址 - 选项卡"
|
||||||
page_network_join_addresses_graphs: "查看加入地址图表"
|
page_network_join_addresses_graphs: "查看加入地址图表"
|
||||||
page_network_join_addresses_graphs_pie: "查看最新加入地址图表"
|
|
||||||
page_network_join_addresses_graphs_time: "查看加入地址随时间变化的图表"
|
page_network_join_addresses_graphs_time: "查看加入地址随时间变化的图表"
|
||||||
page_network_overview: "查看网络总览 - 选项卡"
|
page_network_overview: "查看网络总览 - 选项卡"
|
||||||
page_network_overview_graphs: "查看网络总览图表"
|
page_network_overview_graphs: "查看网络总览图表"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "查看按国家划分的延迟表"
|
page_server_geolocations_ping_per_country: "查看按国家划分的延迟表"
|
||||||
page_server_join_addresses: "查看服务器加入地址 - 选项卡"
|
page_server_join_addresses: "查看服务器加入地址 - 选项卡"
|
||||||
page_server_join_addresses_graphs: "查看服务器加入地址图表"
|
page_server_join_addresses_graphs: "查看服务器加入地址图表"
|
||||||
page_server_join_addresses_graphs_pie: "查看最新加入地址图表"
|
|
||||||
page_server_join_addresses_graphs_time: "查看服务器加入地址随时间变化的图表"
|
page_server_join_addresses_graphs_time: "查看服务器加入地址随时间变化的图表"
|
||||||
page_server_online_activity: "查看在线活动 - 选项卡"
|
page_server_online_activity: "查看在线活动 - 选项卡"
|
||||||
page_server_online_activity_graphs: "查看在线活动图表"
|
page_server_online_activity_graphs: "查看在线活动图表"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Aktivní"
|
active: "Aktivní"
|
||||||
activePlaytime: "Aktivní herní čas"
|
activePlaytime: "Aktivní herní čas"
|
||||||
activityIndex: "Index aktivity"
|
activityIndex: "Index aktivity"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "AFK čas"
|
afkTime: "AFK čas"
|
||||||
all: "Vše"
|
all: "Vše"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Aktiv"
|
active: "Aktiv"
|
||||||
activePlaytime: "Aktive Spielzeit"
|
activePlaytime: "Aktive Spielzeit"
|
||||||
activityIndex: "Aktivitätsindex"
|
activityIndex: "Aktivitätsindex"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "AFK Zeit"
|
afkTime: "AFK Zeit"
|
||||||
all: "Gesamt"
|
all: "Gesamt"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Active"
|
active: "Active"
|
||||||
activePlaytime: "Active Playtime"
|
activePlaytime: "Active Playtime"
|
||||||
activityIndex: "Activity Index"
|
activityIndex: "Activity Index"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "AFK Time"
|
afkTime: "AFK Time"
|
||||||
all: "All"
|
all: "All"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Activo"
|
active: "Activo"
|
||||||
activePlaytime: "Tiempo de juego activo"
|
activePlaytime: "Tiempo de juego activo"
|
||||||
activityIndex: "Índice de actividad"
|
activityIndex: "Índice de actividad"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "Tiempo AFK"
|
afkTime: "Tiempo AFK"
|
||||||
all: "Todo"
|
all: "Todo"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Aktiivinen"
|
active: "Aktiivinen"
|
||||||
activePlaytime: "Aktiivinen peliaika"
|
activePlaytime: "Aktiivinen peliaika"
|
||||||
activityIndex: "Aktiivisuus Indeksi"
|
activityIndex: "Aktiivisuus Indeksi"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "Aika AFK:ina"
|
afkTime: "Aika AFK:ina"
|
||||||
all: "Kaikki"
|
all: "Kaikki"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "Näkee Viive per Maa -taulun"
|
page_network_geolocations_ping_per_country: "Näkee Viive per Maa -taulun"
|
||||||
page_network_join_addresses: "Näkee Liittymäosoitteet osion"
|
page_network_join_addresses: "Näkee Liittymäosoitteet osion"
|
||||||
page_network_join_addresses_graphs: "Näkee Liittymisosoite kaaviot"
|
page_network_join_addresses_graphs: "Näkee Liittymisosoite kaaviot"
|
||||||
page_network_join_addresses_graphs_pie: "Näkee kaavion Viimeisimmistä liittymisosoitteista"
|
|
||||||
page_network_join_addresses_graphs_time: "Näkee kaavion Liittymisosoitteista ajan yli"
|
page_network_join_addresses_graphs_time: "Näkee kaavion Liittymisosoitteista ajan yli"
|
||||||
page_network_overview: "Näkee Verkoston katsaus osion"
|
page_network_overview: "Näkee Verkoston katsaus osion"
|
||||||
page_network_overview_graphs: "Näkee Verkoston katsaus kaaviot"
|
page_network_overview_graphs: "Näkee Verkoston katsaus kaaviot"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "Näkee Viive per Maa -taulun"
|
page_server_geolocations_ping_per_country: "Näkee Viive per Maa -taulun"
|
||||||
page_server_join_addresses: "Näkee Liittymäosoitteet osion"
|
page_server_join_addresses: "Näkee Liittymäosoitteet osion"
|
||||||
page_server_join_addresses_graphs: "Näkee Liittymisosoite kaaviot"
|
page_server_join_addresses_graphs: "Näkee Liittymisosoite kaaviot"
|
||||||
page_server_join_addresses_graphs_pie: "Näkee kaavion Viimeisimmistä liittymisosoitteista"
|
|
||||||
page_server_join_addresses_graphs_time: "Näkee kaavion Liittymisosoitteista ajan yli"
|
page_server_join_addresses_graphs_time: "Näkee kaavion Liittymisosoitteista ajan yli"
|
||||||
page_server_online_activity: "Näkee Online Aktiivisuus osion"
|
page_server_online_activity: "Näkee Online Aktiivisuus osion"
|
||||||
page_server_online_activity_graphs: "Näkee Online Aktiivisuus kaaviot"
|
page_server_online_activity_graphs: "Näkee Online Aktiivisuus kaaviot"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Actif"
|
active: "Actif"
|
||||||
activePlaytime: "Temps Actif"
|
activePlaytime: "Temps Actif"
|
||||||
activityIndex: "Indice d'Activité"
|
activityIndex: "Indice d'Activité"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "Temps AFK"
|
afkTime: "Temps AFK"
|
||||||
all: "Tout"
|
all: "Tout"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "Voir le tableau Ping par pays"
|
page_network_geolocations_ping_per_country: "Voir le tableau Ping par pays"
|
||||||
page_network_join_addresses: "Voir les adresses de jointure -tab"
|
page_network_join_addresses: "Voir les adresses de jointure -tab"
|
||||||
page_network_join_addresses_graphs: "Voir les graphiques de l'adresse de jonction"
|
page_network_join_addresses_graphs: "Voir les graphiques de l'adresse de jonction"
|
||||||
page_network_join_addresses_graphs_pie: "Voir le graphique des dernières adresses de jointure"
|
|
||||||
page_network_join_addresses_graphs_time: "Voir le graphique des adresses de jointure dans le temps"
|
page_network_join_addresses_graphs_time: "Voir le graphique des adresses de jointure dans le temps"
|
||||||
page_network_overview: "Voir Aperçu du réseau -tab"
|
page_network_overview: "Voir Aperçu du réseau -tab"
|
||||||
page_network_overview_graphs: "Voir les graphiques de l'aperçu du réseau"
|
page_network_overview_graphs: "Voir les graphiques de l'aperçu du réseau"
|
||||||
@ -682,7 +683,7 @@ html:
|
|||||||
page_network_plugins: "Voir l'onglet Plugins de Proxy"
|
page_network_plugins: "Voir l'onglet Plugins de Proxy"
|
||||||
page_network_retention: "Voir l'onglet Rétention des joueurs"
|
page_network_retention: "Voir l'onglet Rétention des joueurs"
|
||||||
page_network_server_list: "Voir la liste des serveurs"
|
page_network_server_list: "Voir la liste des serveurs"
|
||||||
page_network_sessions : "Voir l'onglet Sessions"
|
page_network_sessions: "Voir l'onglet Sessions"
|
||||||
page_network_sessions_list: "See list of sessions"
|
page_network_sessions_list: "See list of sessions"
|
||||||
page_network_sessions_overview: "Voir les perspectives de la session"
|
page_network_sessions_overview: "Voir les perspectives de la session"
|
||||||
page_network_sessions_server_pie: "Voir le graphique à secteurs du serveur"
|
page_network_sessions_server_pie: "Voir le graphique à secteurs du serveur"
|
||||||
@ -690,7 +691,7 @@ html:
|
|||||||
page_player: "Voir toute la page du joueur"
|
page_player: "Voir toute la page du joueur"
|
||||||
page_player_overview: "Voir l'aperçu des joueurs -tab"
|
page_player_overview: "Voir l'aperçu des joueurs -tab"
|
||||||
page_player_plugins: "Voir les plugins -tabs"
|
page_player_plugins: "Voir les plugins -tabs"
|
||||||
page_player_servers : "Voir les serveurs -tab"
|
page_player_servers: "Voir les serveurs -tab"
|
||||||
page_player_sessions: "Voir les sessions des joueurs -tab"
|
page_player_sessions: "Voir les sessions des joueurs -tab"
|
||||||
page_player_versus: "Voir PvP & PvE -tab"
|
page_player_versus: "Voir PvP & PvE -tab"
|
||||||
page_server: "Voir toute la page du serveur"
|
page_server: "Voir toute la page du serveur"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "Voir le tableau Ping par pays"
|
page_server_geolocations_ping_per_country: "Voir le tableau Ping par pays"
|
||||||
page_server_join_addresses: "Voir les adresses de jointure -tab"
|
page_server_join_addresses: "Voir les adresses de jointure -tab"
|
||||||
page_server_join_addresses_graphs: "Voir les graphiques de l'adresse de connexion"
|
page_server_join_addresses_graphs: "Voir les graphiques de l'adresse de connexion"
|
||||||
page_server_join_addresses_graphs_pie: "Voir le graphique des dernières adresses de connexion"
|
|
||||||
page_server_join_addresses_graphs_time: "Voir le graphique des adresses de connexion dans le temps"
|
page_server_join_addresses_graphs_time: "Voir le graphique des adresses de connexion dans le temps"
|
||||||
page_server_online_activity: "Voir l'activité en ligne -tab"
|
page_server_online_activity: "Voir l'activité en ligne -tab"
|
||||||
page_server_online_activity_graphs: "Voir les graphiques de l'activité en ligne"
|
page_server_online_activity_graphs: "Voir les graphiques de l'activité en ligne"
|
||||||
@ -725,7 +725,7 @@ html:
|
|||||||
page_server_plugin_history: "Voir l'historique du plugin"
|
page_server_plugin_history: "Voir l'historique du plugin"
|
||||||
page_server_plugins: "Voir les onglets Plugins des serveurs"
|
page_server_plugins: "Voir les onglets Plugins des serveurs"
|
||||||
page_server_retention: "Voir l'onglet Rétention des joueurs"
|
page_server_retention: "Voir l'onglet Rétention des joueurs"
|
||||||
page_server_sessions : "Voir l'onglet Sessions"
|
page_server_sessions: "Voir l'onglet Sessions"
|
||||||
page_server_sessions_list: "Voir la liste des sessions"
|
page_server_sessions_list: "Voir la liste des sessions"
|
||||||
page_server_sessions_overview: "Voir les perspectives de la session"
|
page_server_sessions_overview: "Voir les perspectives de la session"
|
||||||
page_server_sessions_world_pie: "Voir le graphique de la carte du monde"
|
page_server_sessions_world_pie: "Voir le graphique de la carte du monde"
|
||||||
@ -794,6 +794,7 @@ html:
|
|||||||
generic:
|
generic:
|
||||||
are: "`sont`"
|
are: "`sont`"
|
||||||
label:
|
label:
|
||||||
|
editQuery: "Edit Query"
|
||||||
from: ">de</label>"
|
from: ">de</label>"
|
||||||
makeAnother: "Faire une autre Requête"
|
makeAnother: "Faire une autre Requête"
|
||||||
servers:
|
servers:
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Attivo"
|
active: "Attivo"
|
||||||
activePlaytime: "Active Playtime"
|
activePlaytime: "Active Playtime"
|
||||||
activityIndex: "Indice Inattività"
|
activityIndex: "Indice Inattività"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "Tempo AFK"
|
afkTime: "Tempo AFK"
|
||||||
all: "Tutto"
|
all: "Tutto"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "よくログインしている"
|
active: "よくログインしている"
|
||||||
activePlaytime: "アクティブなプレイ時間"
|
activePlaytime: "アクティブなプレイ時間"
|
||||||
activityIndex: "活動指数"
|
activityIndex: "活動指数"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "離席"
|
afk: "離席"
|
||||||
afkTime: "離席時間"
|
afkTime: "離席時間"
|
||||||
all: "全て"
|
all: "全て"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "国ごとのPing表を表示"
|
page_network_geolocations_ping_per_country: "国ごとのPing表を表示"
|
||||||
page_network_join_addresses: "参加アドレスタブを表示"
|
page_network_join_addresses: "参加アドレスタブを表示"
|
||||||
page_network_join_addresses_graphs: "参加アドレスのグラフを表示"
|
page_network_join_addresses_graphs: "参加アドレスのグラフを表示"
|
||||||
page_network_join_addresses_graphs_pie: "最後に参加したアドレスのグラフを表示"
|
|
||||||
page_network_join_addresses_graphs_time: "参加アドレスの経時変化のグラフを表示"
|
page_network_join_addresses_graphs_time: "参加アドレスの経時変化のグラフを表示"
|
||||||
page_network_overview: "ネットワークの概要タブを表示"
|
page_network_overview: "ネットワークの概要タブを表示"
|
||||||
page_network_overview_graphs: "ネットワークの概要グラフを表示"
|
page_network_overview_graphs: "ネットワークの概要グラフを表示"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "国ごとのPing表を表示"
|
page_server_geolocations_ping_per_country: "国ごとのPing表を表示"
|
||||||
page_server_join_addresses: "参加アドレスタブを表示"
|
page_server_join_addresses: "参加アドレスタブを表示"
|
||||||
page_server_join_addresses_graphs: "参加アドレスグラフを表示"
|
page_server_join_addresses_graphs: "参加アドレスグラフを表示"
|
||||||
page_server_join_addresses_graphs_pie: "最後に参加したアドレスのグラフを表示"
|
|
||||||
page_server_join_addresses_graphs_time: "参加アドレスの経時変化のグラフを表示"
|
page_server_join_addresses_graphs_time: "参加アドレスの経時変化のグラフを表示"
|
||||||
page_server_online_activity: "オンラインアクティビティタブを表示"
|
page_server_online_activity: "オンラインアクティビティタブを表示"
|
||||||
page_server_online_activity_graphs: "オンラインアクティビティグラフを表示"
|
page_server_online_activity_graphs: "オンラインアクティビティグラフを表示"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "활동적인"
|
active: "활동적인"
|
||||||
activePlaytime: "Active Playtime"
|
activePlaytime: "Active Playtime"
|
||||||
activityIndex: "활동 색인"
|
activityIndex: "활동 색인"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "AFK 시간"
|
afkTime: "AFK 시간"
|
||||||
all: "모두"
|
all: "모두"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Actief"
|
active: "Actief"
|
||||||
activePlaytime: "Actieve Speeltijd"
|
activePlaytime: "Actieve Speeltijd"
|
||||||
activityIndex: "Activiteitsindex"
|
activityIndex: "Activiteitsindex"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "AFK Tijd"
|
afkTime: "AFK Tijd"
|
||||||
all: "Alle"
|
all: "Alle"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Ativo"
|
active: "Ativo"
|
||||||
activePlaytime: "Active Playtime"
|
activePlaytime: "Active Playtime"
|
||||||
activityIndex: "Índice de Atividade"
|
activityIndex: "Índice de Atividade"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "AFK Time"
|
afkTime: "AFK Time"
|
||||||
all: "Todos"
|
all: "Todos"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Активный"
|
active: "Активный"
|
||||||
activePlaytime: "Активное время игры"
|
activePlaytime: "Активное время игры"
|
||||||
activityIndex: "Индекс активности"
|
activityIndex: "Индекс активности"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "Время AFK"
|
afkTime: "Время AFK"
|
||||||
all: "Все"
|
all: "Все"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Aktivite"
|
active: "Aktivite"
|
||||||
activePlaytime: "Aktif Oyun Süresi"
|
activePlaytime: "Aktif Oyun Süresi"
|
||||||
activityIndex: "Aktivite göstergesi"
|
activityIndex: "Aktivite göstergesi"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "AFK Süresi"
|
afkTime: "AFK Süresi"
|
||||||
all: "Tamamı"
|
all: "Tamamı"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "Активний"
|
active: "Активний"
|
||||||
activePlaytime: "Активний час гри"
|
activePlaytime: "Активний час гри"
|
||||||
activityIndex: "Індекс активності"
|
activityIndex: "Індекс активності"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "AFK"
|
afk: "AFK"
|
||||||
afkTime: "Час AFK"
|
afkTime: "Час AFK"
|
||||||
all: "Всі"
|
all: "Всі"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -290,6 +290,8 @@ html:
|
|||||||
active: "活躍"
|
active: "活躍"
|
||||||
activePlaytime: "活躍時間"
|
activePlaytime: "活躍時間"
|
||||||
activityIndex: "活躍指數"
|
activityIndex: "活躍指數"
|
||||||
|
addJoinAddressGroup: "Add address group"
|
||||||
|
addressGroup: "Address group {{n}}"
|
||||||
afk: "掛機"
|
afk: "掛機"
|
||||||
afkTime: "掛機時間"
|
afkTime: "掛機時間"
|
||||||
all: "全部"
|
all: "全部"
|
||||||
@ -664,7 +666,6 @@ html:
|
|||||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_network_join_addresses: "See Join Addresses -tab"
|
page_network_join_addresses: "See Join Addresses -tab"
|
||||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_network_overview: "See Network Overview -tab"
|
page_network_overview: "See Network Overview -tab"
|
||||||
page_network_overview_graphs: "See Network Overview graphs"
|
page_network_overview_graphs: "See Network Overview graphs"
|
||||||
@ -700,7 +701,6 @@ html:
|
|||||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||||
page_server_join_addresses: "See Join Addresses -tab"
|
page_server_join_addresses: "See Join Addresses -tab"
|
||||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
|
||||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||||
page_server_online_activity: "See Online Activity -tab"
|
page_server_online_activity: "See Online Activity -tab"
|
||||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.delivery.domain.auth;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link WebPermission}.
|
||||||
|
*
|
||||||
|
* @author AuroraLS3
|
||||||
|
*/
|
||||||
|
class WebPermissionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void webPermissionIsFound() {
|
||||||
|
String permission = "access.player.self";
|
||||||
|
WebPermission found = WebPermission.findByPermission(permission).orElseThrow(AssertionError::new);
|
||||||
|
WebPermission expected = WebPermission.ACCESS_PLAYER_SELF;
|
||||||
|
assertEquals(expected, found);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void webPermissionIsDetectedAsDeprecated() {
|
||||||
|
String permission = "page.server.join.addresses.graphs.pie";
|
||||||
|
assertTrue(WebPermission.isDeprecated(permission));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void webPermissionIsDetectedAsNonDeprecated() {
|
||||||
|
String permission = "access.player.self";
|
||||||
|
assertFalse(WebPermission.isDeprecated(permission));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void customWebPermissionIsDetectedAsNonDeprecated() {
|
||||||
|
String permission = "custom.permission";
|
||||||
|
assertFalse(WebPermission.isDeprecated(permission));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -95,7 +95,6 @@ class AccessControlTest {
|
|||||||
Arguments.of("/v1/graph?type=aggregatedPing&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PERFORMANCE_GRAPHS, 200, 403),
|
Arguments.of("/v1/graph?type=aggregatedPing&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PERFORMANCE_GRAPHS, 200, 403),
|
||||||
Arguments.of("/v1/graph?type=worldPie&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_SESSIONS_WORLD_PIE, 200, 403),
|
Arguments.of("/v1/graph?type=worldPie&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_SESSIONS_WORLD_PIE, 200, 403),
|
||||||
Arguments.of("/v1/graph?type=activity&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS, 200, 403),
|
Arguments.of("/v1/graph?type=activity&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS, 200, 403),
|
||||||
Arguments.of("/v1/graph?type=joinAddressPie&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE, 200, 403),
|
|
||||||
Arguments.of("/v1/graph?type=geolocation&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP, 200, 403),
|
Arguments.of("/v1/graph?type=geolocation&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP, 200, 403),
|
||||||
Arguments.of("/v1/graph?type=uniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_DAY_BY_DAY, 200, 403),
|
Arguments.of("/v1/graph?type=uniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_DAY_BY_DAY, 200, 403),
|
||||||
Arguments.of("/v1/graph?type=hourlyUniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_HOUR_BY_HOUR, 200, 403),
|
Arguments.of("/v1/graph?type=hourlyUniqueAndNew&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_HOUR_BY_HOUR, 200, 403),
|
||||||
@ -107,7 +106,10 @@ class AccessControlTest {
|
|||||||
Arguments.of("/v1/pingTable?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_GEOLOCATIONS_PING_PER_COUNTRY, 200, 403),
|
Arguments.of("/v1/pingTable?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_GEOLOCATIONS_PING_PER_COUNTRY, 200, 403),
|
||||||
Arguments.of("/v1/sessions?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_SESSIONS_LIST, 200, 403),
|
Arguments.of("/v1/sessions?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_SESSIONS_LIST, 200, 403),
|
||||||
Arguments.of("/v1/retention?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_RETENTION, 200, 403),
|
Arguments.of("/v1/retention?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_RETENTION, 200, 403),
|
||||||
|
Arguments.of("/v1/joinAddresses", WebPermission.PAGE_NETWORK_RETENTION, 200, 403),
|
||||||
|
Arguments.of("/v1/joinAddresses?listOnly=true", WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME, 200, 403),
|
||||||
Arguments.of("/v1/joinAddresses?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_RETENTION, 200, 403),
|
Arguments.of("/v1/joinAddresses?server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_RETENTION, 200, 403),
|
||||||
|
Arguments.of("/v1/joinAddresses?server=" + TestConstants.SERVER_UUID_STRING + "&listOnly=true", WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME, 200, 403),
|
||||||
Arguments.of("/network", WebPermission.ACCESS_NETWORK, 302, 403),
|
Arguments.of("/network", WebPermission.ACCESS_NETWORK, 302, 403),
|
||||||
Arguments.of("/v1/network/overview", WebPermission.PAGE_NETWORK_OVERVIEW_NUMBERS, 200, 403),
|
Arguments.of("/v1/network/overview", WebPermission.PAGE_NETWORK_OVERVIEW_NUMBERS, 200, 403),
|
||||||
Arguments.of("/v1/network/servers", WebPermission.PAGE_NETWORK_SERVER_LIST, 200, 403),
|
Arguments.of("/v1/network/servers", WebPermission.PAGE_NETWORK_SERVER_LIST, 200, 403),
|
||||||
@ -119,7 +121,6 @@ class AccessControlTest {
|
|||||||
Arguments.of("/v1/graph?type=hourlyUniqueAndNew", WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_HOUR_BY_HOUR, 200, 403),
|
Arguments.of("/v1/graph?type=hourlyUniqueAndNew", WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_HOUR_BY_HOUR, 200, 403),
|
||||||
Arguments.of("/v1/graph?type=serverCalendar", WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_CALENDAR, 200, 403),
|
Arguments.of("/v1/graph?type=serverCalendar", WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_CALENDAR, 200, 403),
|
||||||
Arguments.of("/v1/graph?type=serverPie", WebPermission.PAGE_NETWORK_SESSIONS_SERVER_PIE, 200, 403),
|
Arguments.of("/v1/graph?type=serverPie", WebPermission.PAGE_NETWORK_SESSIONS_SERVER_PIE, 200, 403),
|
||||||
Arguments.of("/v1/graph?type=joinAddressPie", WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE, 200, 403),
|
|
||||||
Arguments.of("/v1/graph?type=activity", WebPermission.PAGE_NETWORK_PLAYERBASE_GRAPHS, 200, 403),
|
Arguments.of("/v1/graph?type=activity", WebPermission.PAGE_NETWORK_PLAYERBASE_GRAPHS, 200, 403),
|
||||||
Arguments.of("/v1/graph?type=geolocation", WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP, 200, 403),
|
Arguments.of("/v1/graph?type=geolocation", WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP, 200, 403),
|
||||||
Arguments.of("/v1/network/pingTable", WebPermission.PAGE_NETWORK_GEOLOCATIONS_PING_PER_COUNTRY, 200, 403),
|
Arguments.of("/v1/network/pingTable", WebPermission.PAGE_NETWORK_GEOLOCATIONS_PING_PER_COUNTRY, 200, 403),
|
||||||
|
@ -141,8 +141,7 @@ class AccessControlVisibilityTest {
|
|||||||
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_OVERVIEW, "playerbase-insights", "playerbase"),
|
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_OVERVIEW, "playerbase-insights", "playerbase"),
|
||||||
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS, "playerbase-graph", "playerbase"),
|
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS, "playerbase-graph", "playerbase"),
|
||||||
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS, "playerbase-current", "playerbase"),
|
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS, "playerbase-current", "playerbase"),
|
||||||
Arguments.arguments(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME, "join-address-graph", "join-addresses"),
|
Arguments.arguments(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME, "server-join-addresses", "join-addresses"),
|
||||||
Arguments.arguments(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE, "join-address-groups", "join-addresses"),
|
|
||||||
Arguments.arguments(WebPermission.PAGE_SERVER_RETENTION, "retention-graph", "retention"),
|
Arguments.arguments(WebPermission.PAGE_SERVER_RETENTION, "retention-graph", "retention"),
|
||||||
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERS, "players-table", "players"),
|
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERS, "players-table", "players"),
|
||||||
Arguments.arguments(WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP, "geolocations", "geolocations"),
|
Arguments.arguments(WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP, "geolocations", "geolocations"),
|
||||||
@ -172,8 +171,7 @@ class AccessControlVisibilityTest {
|
|||||||
Arguments.arguments(WebPermission.PAGE_NETWORK_PLAYERBASE_OVERVIEW, "playerbase-insights", "playerbase"),
|
Arguments.arguments(WebPermission.PAGE_NETWORK_PLAYERBASE_OVERVIEW, "playerbase-insights", "playerbase"),
|
||||||
Arguments.arguments(WebPermission.PAGE_NETWORK_PLAYERBASE_GRAPHS, "playerbase-graph", "playerbase"),
|
Arguments.arguments(WebPermission.PAGE_NETWORK_PLAYERBASE_GRAPHS, "playerbase-graph", "playerbase"),
|
||||||
Arguments.arguments(WebPermission.PAGE_NETWORK_PLAYERBASE_GRAPHS, "playerbase-current", "playerbase"),
|
Arguments.arguments(WebPermission.PAGE_NETWORK_PLAYERBASE_GRAPHS, "playerbase-current", "playerbase"),
|
||||||
Arguments.arguments(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME, "join-address-graph", "join-addresses"),
|
Arguments.arguments(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME, "network-join-addresses", "join-addresses"),
|
||||||
Arguments.arguments(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE, "join-address-groups", "join-addresses"),
|
|
||||||
Arguments.arguments(WebPermission.PAGE_NETWORK_RETENTION, "retention-graph", "retention"),
|
Arguments.arguments(WebPermission.PAGE_NETWORK_RETENTION, "retention-graph", "retention"),
|
||||||
Arguments.arguments(WebPermission.PAGE_NETWORK_PLAYERS, "players-table", "players"),
|
Arguments.arguments(WebPermission.PAGE_NETWORK_PLAYERS, "players-table", "players"),
|
||||||
Arguments.arguments(WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP, "geolocations", "geolocations"),
|
Arguments.arguments(WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP, "geolocations", "geolocations"),
|
||||||
|
@ -264,13 +264,5 @@ class PlayerLeaveEventConsumerTest {
|
|||||||
List<String> expected = List.of("PLAY.UPPERCASE.COM", "play.uppercase.com", JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
List<String> expected = List.of("PLAY.UPPERCASE.COM", "play.uppercase.com", JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||||
List<String> result = database.query(JoinAddressQueries.allJoinAddresses());
|
List<String> result = database.query(JoinAddressQueries.allJoinAddresses());
|
||||||
assertEquals(expected, result);
|
assertEquals(expected, result);
|
||||||
|
|
||||||
Map<String, Integer> expectedMap = Map.of("PLAY.UPPERCASE.COM", 1);
|
|
||||||
Map<String, Integer> resultMap = database.query(JoinAddressQueries.latestJoinAddresses(serverUUID));
|
|
||||||
assertEquals(expectedMap, resultMap);
|
|
||||||
|
|
||||||
expectedMap = Map.of("PLAY.UPPERCASE.COM", 1);
|
|
||||||
resultMap = database.query(JoinAddressQueries.latestJoinAddresses());
|
|
||||||
assertEquals(expectedMap, resultMap);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,8 +16,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.djrapitops.plan.storage.database.queries;
|
package com.djrapitops.plan.storage.database.queries;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.domain.DateObj;
|
||||||
import com.djrapitops.plan.gathering.domain.FinishedSession;
|
import com.djrapitops.plan.gathering.domain.FinishedSession;
|
||||||
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
|
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
|
||||||
|
import com.djrapitops.plan.identification.ServerUUID;
|
||||||
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
|
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
|
||||||
import com.djrapitops.plan.storage.database.DatabaseTestPreparer;
|
import com.djrapitops.plan.storage.database.DatabaseTestPreparer;
|
||||||
import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries;
|
import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries;
|
||||||
@ -27,12 +29,14 @@ import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
|
|||||||
import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction;
|
import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction;
|
||||||
import com.djrapitops.plan.storage.database.transactions.events.*;
|
import com.djrapitops.plan.storage.database.transactions.events.*;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import utilities.RandomData;
|
import utilities.RandomData;
|
||||||
import utilities.TestConstants;
|
import utilities.TestConstants;
|
||||||
import utilities.TestData;
|
import utilities.TestData;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
@ -120,6 +124,55 @@ public interface JoinAddressQueriesTest extends DatabaseTestPreparer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Join address by day is filtered by addresses")
|
||||||
|
default void joinAddressListIsFilteredByAddress() {
|
||||||
|
db().executeTransaction(TestData.storeServers());
|
||||||
|
|
||||||
|
db().executeTransaction(new StoreWorldNameTransaction(TestConstants.SERVER_TWO_UUID, worlds[0]));
|
||||||
|
db().executeTransaction(new StoreWorldNameTransaction(TestConstants.SERVER_TWO_UUID, worlds[1]));
|
||||||
|
FinishedSession session = RandomData.randomSession(TestConstants.SERVER_TWO_UUID, worlds, playerUUID, player2UUID);
|
||||||
|
String expectedAddress = TestConstants.GET_PLAYER_HOSTNAME.get();
|
||||||
|
session.getExtraData().put(JoinAddress.class, new JoinAddress(expectedAddress));
|
||||||
|
db().executeTransaction(new StoreSessionTransaction(session));
|
||||||
|
|
||||||
|
List<DateObj<Map<String, Integer>>> result = db().query(JoinAddressQueries.joinAddressesPerDay(0, 0, System.currentTimeMillis(), List.of("nonexistent.com")));
|
||||||
|
assertEquals(List.of(), result);
|
||||||
|
|
||||||
|
long startOfDay = session.getDate() - session.getDate() % TimeUnit.DAYS.toMillis(1);
|
||||||
|
|
||||||
|
List<DateObj<Map<String, Integer>>> result2 = db().query(JoinAddressQueries.joinAddressesPerDay(0, 0, System.currentTimeMillis(), List.of(expectedAddress)));
|
||||||
|
assertEquals(List.of(new DateObj<>(startOfDay, Map.of(expectedAddress, 1))), result2);
|
||||||
|
|
||||||
|
List<DateObj<Map<String, Integer>>> result3 = db().query(JoinAddressQueries.joinAddressesPerDay(0, 0, System.currentTimeMillis(), List.of()));
|
||||||
|
assertEquals(List.of(new DateObj<>(startOfDay, Map.of(expectedAddress, 1))), result3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Server join address by day is filtered by addresses")
|
||||||
|
default void serverJoinAddressListIsFilteredByAddress() {
|
||||||
|
db().executeTransaction(TestData.storeServers());
|
||||||
|
|
||||||
|
ServerUUID serverTwoUuid = TestConstants.SERVER_TWO_UUID;
|
||||||
|
db().executeTransaction(new StoreWorldNameTransaction(serverTwoUuid, worlds[0]));
|
||||||
|
db().executeTransaction(new StoreWorldNameTransaction(serverTwoUuid, worlds[1]));
|
||||||
|
FinishedSession session = RandomData.randomSession(serverTwoUuid, worlds, playerUUID, player2UUID);
|
||||||
|
String expectedAddress = TestConstants.GET_PLAYER_HOSTNAME.get();
|
||||||
|
session.getExtraData().put(JoinAddress.class, new JoinAddress(expectedAddress));
|
||||||
|
db().executeTransaction(new StoreSessionTransaction(session));
|
||||||
|
|
||||||
|
List<DateObj<Map<String, Integer>>> result = db().query(JoinAddressQueries.joinAddressesPerDay(serverTwoUuid, 0, 0, System.currentTimeMillis(), List.of("nonexistent.com")));
|
||||||
|
assertEquals(List.of(), result);
|
||||||
|
|
||||||
|
long startOfDay = session.getDate() - session.getDate() % TimeUnit.DAYS.toMillis(1);
|
||||||
|
|
||||||
|
List<DateObj<Map<String, Integer>>> result2 = db().query(JoinAddressQueries.joinAddressesPerDay(serverTwoUuid, 0, 0, System.currentTimeMillis(), List.of(expectedAddress)));
|
||||||
|
assertEquals(List.of(new DateObj<>(startOfDay, Map.of(expectedAddress, 1))), result2);
|
||||||
|
|
||||||
|
List<DateObj<Map<String, Integer>>> result3 = db().query(JoinAddressQueries.joinAddressesPerDay(serverTwoUuid, 0, 0, System.currentTimeMillis(), List.of()));
|
||||||
|
assertEquals(List.of(new DateObj<>(startOfDay, Map.of(expectedAddress, 1))), result3);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
default void joinAddressIsTruncated() {
|
default void joinAddressIsTruncated() {
|
||||||
db().executeTransaction(new StoreWorldNameTransaction(serverUUID(), worlds[0]));
|
db().executeTransaction(new StoreWorldNameTransaction(serverUUID(), worlds[0]));
|
||||||
@ -201,15 +254,6 @@ public interface JoinAddressQueriesTest extends DatabaseTestPreparer {
|
|||||||
assertEquals(expected, result);
|
assertEquals(expected, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
default void serverJoinAddressQueryHasNoNullValues() {
|
|
||||||
joinAddressCanBeUnknown();
|
|
||||||
|
|
||||||
Map<String, Integer> expected = Collections.singletonMap(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP, 1);
|
|
||||||
Map<String, Integer> result = db().query(JoinAddressQueries.latestJoinAddresses(serverUUID()));
|
|
||||||
assertEquals(expected, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
default void joinAddressQueryHasDistinctPlayers() {
|
default void joinAddressQueryHasDistinctPlayers() {
|
||||||
joinAddressCanBeUnknown();
|
joinAddressCanBeUnknown();
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import React, {useCallback, useEffect, useState} from "react";
|
||||||
|
import {Card, Form} from "react-bootstrap";
|
||||||
|
import CardHeader from "../CardHeader.jsx";
|
||||||
|
import {faCheck, faList, faPencil} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import MultiSelect from "../../input/MultiSelect.jsx";
|
||||||
|
import {faTrashAlt} from "@fortawesome/free-regular-svg-icons";
|
||||||
|
|
||||||
|
const AddressGroupCard = ({n, group, editGroup, allAddresses, remove}) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const [selectedIndexes, setSelectedIndexes] = useState([]);
|
||||||
|
const [editingName, setEditingName] = useState(false);
|
||||||
|
const [name, setName] = useState(group.name);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedIndexes.length && allAddresses?.length && group?.addresses?.length) {
|
||||||
|
setSelectedIndexes(group.addresses
|
||||||
|
.map(address => allAddresses.indexOf(address))
|
||||||
|
.filter(index => index !== -1)) // Make sure addresses are not selected that no longer exist
|
||||||
|
}
|
||||||
|
}, [selectedIndexes, group, allAddresses])
|
||||||
|
|
||||||
|
const applySelected = useCallback(() => {
|
||||||
|
editGroup({...group, addresses: allAddresses.filter((a, i) => selectedIndexes.includes(i))})
|
||||||
|
}, [editGroup, group, allAddresses, selectedIndexes]);
|
||||||
|
const editName = useCallback(newName => {
|
||||||
|
editGroup({...group, name: newName});
|
||||||
|
}, [editGroup, group]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editingName && name !== group.name) editName(name);
|
||||||
|
}, [editName, editingName, name])
|
||||||
|
|
||||||
|
const selectedAddresses = allAddresses.filter((a, i) => selectedIndexes.includes(i));
|
||||||
|
const isUpToDate = !selectedIndexes.length || selectedAddresses.length === group.addresses.length && selectedAddresses.every((a, i) => a === group.addresses[i]);
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader icon={faList} color={"amber"} label={
|
||||||
|
editingName ?
|
||||||
|
<Form.Control
|
||||||
|
style={{position: "absolute", top: "0.5rem", left: "2.5rem", width: "calc(100% - 3rem)"}}
|
||||||
|
value={name}
|
||||||
|
onChange={e => setName(e.target.value)}/> : group.name
|
||||||
|
}>
|
||||||
|
<button
|
||||||
|
style={editingName ? {position: "absolute", right: "1rem", top: "1.2rem"} : {marginLeft: "0.5rem"}}
|
||||||
|
onClick={() => setEditingName(!editingName)}>
|
||||||
|
<FontAwesomeIcon icon={editingName ? faCheck : faPencil}/>
|
||||||
|
</button>
|
||||||
|
</CardHeader>
|
||||||
|
<Card.Body>
|
||||||
|
<MultiSelect options={allAddresses} selectedIndexes={selectedIndexes}
|
||||||
|
setSelectedIndexes={setSelectedIndexes}/>
|
||||||
|
<button className={'mt-2 btn ' + (isUpToDate ? 'bg-transparent' : 'bg-theme')}
|
||||||
|
onClick={applySelected} disabled={isUpToDate}>
|
||||||
|
{t('html.label.apply')}
|
||||||
|
</button>
|
||||||
|
<button className={'mt-2 btn btn-outline-secondary float-end'}
|
||||||
|
onClick={remove}>
|
||||||
|
<FontAwesomeIcon icon={faTrashAlt}/>
|
||||||
|
</button>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default AddressGroupCard;
|
@ -0,0 +1,32 @@
|
|||||||
|
import {Col, Row} from "react-bootstrap";
|
||||||
|
import AddressGroupCard from "./AddressGroupCard.jsx";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faPlus} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import React from "react";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {useJoinAddressListContext} from "../../../hooks/context/joinAddressListContextHook.jsx";
|
||||||
|
|
||||||
|
const AddressGroupSelectorRow = () => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const {list, add, remove, replace, allAddresses} = useJoinAddressListContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row id={"address-selector"}>
|
||||||
|
{list.map((group, i) =>
|
||||||
|
<Col lg={2} key={group.uuid}>
|
||||||
|
<AddressGroupCard n={i + 1}
|
||||||
|
group={group}
|
||||||
|
editGroup={replacement => replace(replacement, i)}
|
||||||
|
allAddresses={allAddresses}
|
||||||
|
remove={() => remove(i)}/>
|
||||||
|
</Col>)}
|
||||||
|
<Col lg={2}>
|
||||||
|
<button className={"btn bg-theme mb-4"} onClick={add}>
|
||||||
|
<FontAwesomeIcon icon={faPlus}/> {t('html.label.addJoinAddressGroup')}
|
||||||
|
</button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddressGroupSelectorRow;
|
@ -0,0 +1,27 @@
|
|||||||
|
import LoadIn from "../../animation/LoadIn.jsx";
|
||||||
|
import ExtendableRow from "../../layout/extension/ExtendableRow.jsx";
|
||||||
|
import JoinAddressGraphCard from "../server/graphs/JoinAddressGraphCard.jsx";
|
||||||
|
import {Col} from "react-bootstrap";
|
||||||
|
import React from "react";
|
||||||
|
import AddressGroupSelectorRow from "./AddressGroupSelectorRow.jsx";
|
||||||
|
import {JoinAddressListContextProvider} from "../../../hooks/context/joinAddressListContextHook.jsx";
|
||||||
|
import {staticSite} from "../../../service/backendConfiguration.js";
|
||||||
|
|
||||||
|
const JoinAddresses = ({id, seeTime, identifier}) => {
|
||||||
|
return (
|
||||||
|
<LoadIn>
|
||||||
|
{seeTime && <section id={id} className={id}>
|
||||||
|
<JoinAddressListContextProvider identifier={identifier} isAllowed={seeTime}>
|
||||||
|
<ExtendableRow id={`row-${id}-0`}>
|
||||||
|
<Col lg={12}>
|
||||||
|
<JoinAddressGraphCard identifier={identifier}/>
|
||||||
|
</Col>
|
||||||
|
</ExtendableRow>
|
||||||
|
{!staticSite && <AddressGroupSelectorRow/>}
|
||||||
|
</JoinAddressListContextProvider>
|
||||||
|
</section>}
|
||||||
|
</LoadIn>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JoinAddresses;
|
@ -0,0 +1,30 @@
|
|||||||
|
import LoadIn from "../../animation/LoadIn.jsx";
|
||||||
|
import ExtendableRow from "../../layout/extension/ExtendableRow.jsx";
|
||||||
|
import {Col} from "react-bootstrap";
|
||||||
|
import PlayerRetentionGraphCard from "./PlayerRetentionGraphCard.jsx";
|
||||||
|
import React, {useState} from "react";
|
||||||
|
import {JoinAddressListContextProvider} from "../../../hooks/context/joinAddressListContextHook.jsx";
|
||||||
|
import AddressGroupSelectorRow from "./AddressGroupSelectorRow.jsx";
|
||||||
|
|
||||||
|
const PlayerRetention = ({id, seeRetention, identifier}) => {
|
||||||
|
const [selectedGroupBy, setSelectedGroupBy] = useState('none');
|
||||||
|
return (
|
||||||
|
<LoadIn>
|
||||||
|
{seeRetention && <section id={id} className={id}>
|
||||||
|
<JoinAddressListContextProvider identifier={identifier} isAllowed={seeRetention}
|
||||||
|
loadIndividualAddresses>
|
||||||
|
<ExtendableRow id={`row-${id}-0`}>
|
||||||
|
<Col lg={12}>
|
||||||
|
<PlayerRetentionGraphCard identifier={identifier}
|
||||||
|
selectedGroupBy={selectedGroupBy}
|
||||||
|
setSelectedGroupBy={setSelectedGroupBy}/>
|
||||||
|
</Col>
|
||||||
|
</ExtendableRow>
|
||||||
|
{selectedGroupBy === 'joinAddress' && <AddressGroupSelectorRow/>}
|
||||||
|
</JoinAddressListContextProvider>
|
||||||
|
</section>}
|
||||||
|
</LoadIn>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlayerRetention
|
@ -17,6 +17,7 @@ import {useTheme} from "../../../hooks/themeHook";
|
|||||||
import {useNavigation} from "../../../hooks/navigationHook";
|
import {useNavigation} from "../../../hooks/navigationHook";
|
||||||
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
||||||
import {faQuestionCircle} from "@fortawesome/free-regular-svg-icons";
|
import {faQuestionCircle} from "@fortawesome/free-regular-svg-icons";
|
||||||
|
import {useJoinAddressListContext} from "../../../hooks/context/joinAddressListContextHook.jsx";
|
||||||
|
|
||||||
const dayMs = 24 * 3600000;
|
const dayMs = 24 * 3600000;
|
||||||
const getWeek = (date) => {
|
const getWeek = (date) => {
|
||||||
@ -26,7 +27,7 @@ const getWeek = (date) => {
|
|||||||
return Math.ceil(dayOfYear / 7)
|
return Math.ceil(dayOfYear / 7)
|
||||||
};
|
};
|
||||||
|
|
||||||
const PlayerRetentionGraphCard = ({identifier}) => {
|
const PlayerRetentionGraphCard = ({identifier, selectedGroupBy, setSelectedGroupBy}) => {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const {nightModeEnabled} = useTheme();
|
const {nightModeEnabled} = useTheme();
|
||||||
const {setHelpModalTopic} = useNavigation();
|
const {setHelpModalTopic} = useNavigation();
|
||||||
@ -40,6 +41,8 @@ const PlayerRetentionGraphCard = ({identifier}) => {
|
|||||||
loadingError: joinAddressLoadingError
|
loadingError: joinAddressLoadingError
|
||||||
} = useDataRequest(fetchPlayerJoinAddresses, [identifier]);
|
} = useDataRequest(fetchPlayerJoinAddresses, [identifier]);
|
||||||
|
|
||||||
|
const {list, playerAddresses} = useJoinAddressListContext();
|
||||||
|
|
||||||
const [selectedWindow, setSelectedWindow] = useState('days');
|
const [selectedWindow, setSelectedWindow] = useState('days');
|
||||||
const windowOptions = useMemo(() => [
|
const windowOptions = useMemo(() => [
|
||||||
{name: 'hours', displayName: t('html.label.time.hours'), increment: 3600000},
|
{name: 'hours', displayName: t('html.label.time.hours'), increment: 3600000},
|
||||||
@ -57,7 +60,7 @@ const PlayerRetentionGraphCard = ({identifier}) => {
|
|||||||
{name: 'registered-2y', displayName: t('html.label.retention.inLast730d'), start: time - 2 * 365 * dayMs},
|
{name: 'registered-2y', displayName: t('html.label.retention.inLast730d'), start: time - 2 * 365 * dayMs},
|
||||||
{name: 'registered-ever', displayName: t('html.label.retention.inAnytime'), start: 0},
|
{name: 'registered-ever', displayName: t('html.label.retention.inAnytime'), start: 0},
|
||||||
], [t, time]);
|
], [t, time]);
|
||||||
const [selectedGroupBy, setSelectedGroupBy] = useState('none');
|
// State moved to higher level for join address group selection
|
||||||
const groupByOptions = useMemo(() => [
|
const groupByOptions = useMemo(() => [
|
||||||
{name: 'none', displayName: t('html.label.retention.groupByNone')},
|
{name: 'none', displayName: t('html.label.retention.groupByNone')},
|
||||||
{name: 'days', displayName: t('html.label.time.day')},
|
{name: 'days', displayName: t('html.label.time.day')},
|
||||||
@ -165,8 +168,11 @@ const PlayerRetentionGraphCard = ({identifier}) => {
|
|||||||
break;
|
break;
|
||||||
case 'joinAddress':
|
case 'joinAddress':
|
||||||
const joinAddress = joinAddressData[point.playerUUID];
|
const joinAddress = joinAddressData[point.playerUUID];
|
||||||
if (!grouped[joinAddress]) grouped[joinAddress] = [];
|
const joinAddressGroups = list.filter(g => g.addresses.includes(joinAddress)).map(g => g.name);
|
||||||
grouped[joinAddress].push(point);
|
for (const joinAddressGroup of joinAddressGroups) {
|
||||||
|
if (!grouped[joinAddressGroup]) grouped[joinAddressGroup] = [];
|
||||||
|
grouped[joinAddressGroup].push(point);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'none':
|
case 'none':
|
||||||
default:
|
default:
|
||||||
@ -175,7 +181,7 @@ const PlayerRetentionGraphCard = ({identifier}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return grouped;
|
return grouped;
|
||||||
}, [groupByOptions, selectedGroupBy]);
|
}, [groupByOptions, selectedGroupBy, list]);
|
||||||
|
|
||||||
const createSeries = useCallback(async (retentionData, joinAddressData) => {
|
const createSeries = useCallback(async (retentionData, joinAddressData) => {
|
||||||
|
|
||||||
@ -207,10 +213,10 @@ const PlayerRetentionGraphCard = ({identifier}) => {
|
|||||||
}, [nightModeEnabled, mapToData, groupOptions, selectedGroup, selectedYAxis, group]);
|
}, [nightModeEnabled, mapToData, groupOptions, selectedGroup, selectedYAxis, group]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data || !joinAddressData) return;
|
if (!data || !playerAddresses) return;
|
||||||
|
|
||||||
createSeries(data.player_retention, joinAddressData.join_address_by_player).then(series => setSeries(series.flat()));
|
createSeries(data.player_retention, playerAddresses).then(series => setSeries(series.flat()));
|
||||||
}, [data, joinAddressData, createSeries, setSeries]);
|
}, [data, playerAddresses, createSeries, setSeries]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const windowName = windowOptions.find(option => option.name === selectedWindow).displayName;
|
const windowName = windowOptions.find(option => option.name === selectedWindow).displayName;
|
||||||
@ -261,13 +267,15 @@ const PlayerRetentionGraphCard = ({identifier}) => {
|
|||||||
},
|
},
|
||||||
tooltip: selectedAxis === 'date' || selectedAxis === 'deltas' ? {
|
tooltip: selectedAxis === 'date' || selectedAxis === 'deltas' ? {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
shared: series.length <= 10,
|
||||||
valueDecimals: 2,
|
valueDecimals: 2,
|
||||||
pointFormat: (selectedGroupBy !== 'none' ? '{series.name} - ' : '') + '<b>{point.y} ' + (selectedYAxis === 'percentage' ? '%' : t('html.label.players')) + '</b>'
|
pointFormat: (selectedGroupBy !== 'none' ? '{series.name} - ' : '') + '<b>{point.y} ' + (selectedYAxis === 'percentage' ? '%' : t('html.label.players')) + '</b><br>'
|
||||||
} : {
|
} : {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
shared: series.length <= 10,
|
||||||
valueDecimals: 2,
|
valueDecimals: 2,
|
||||||
headerFormat: '{point.x} ' + windowName + '<br>',
|
headerFormat: '{point.x} ' + windowName + '<br>',
|
||||||
pointFormat: (selectedGroupBy !== 'none' ? '{series.name} - ' : '') + '<b>{point.y} ' + (selectedYAxis === 'percentage' ? '%' : t('html.label.players')) + '</b>'
|
pointFormat: (selectedGroupBy !== 'none' ? '{series.name} - ' : '') + '<b>{point.y} ' + (selectedYAxis === 'percentage' ? '%' : t('html.label.players')) + '</b><br>'
|
||||||
},
|
},
|
||||||
series: series
|
series: series
|
||||||
})
|
})
|
||||||
|
@ -1,24 +1,80 @@
|
|||||||
import React, {useState} from 'react';
|
import React, {useCallback, useEffect, useState} from 'react';
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
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";
|
||||||
import {CardLoader} from "../../../navigation/Loader";
|
import {ChartLoader} from "../../../navigation/Loader";
|
||||||
import {Card} from "react-bootstrap";
|
import {Card} from "react-bootstrap";
|
||||||
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 "../../../input/Toggle";
|
import Toggle from "../../../input/Toggle";
|
||||||
|
import {useJoinAddressListContext} from "../../../../hooks/context/joinAddressListContextHook.jsx";
|
||||||
|
import {useNavigation} from "../../../../hooks/navigationHook.jsx";
|
||||||
|
import {staticSite} from "../../../../service/backendConfiguration.js";
|
||||||
|
|
||||||
const JoinAddressGraphCard = ({identifier}) => {
|
const JoinAddressGraphCard = ({identifier}) => {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const [stack, setStack] = useState(true);
|
const [stack, setStack] = useState(true);
|
||||||
|
const {updateRequested} = useNavigation();
|
||||||
|
|
||||||
const {data, loadingError} = useDataRequest(fetchJoinAddressByDay, [identifier]);
|
const {list} = useJoinAddressListContext();
|
||||||
|
const noSelectedAddresses = !list.filter(group => group.addresses.length).length;
|
||||||
|
|
||||||
|
const [data, setData] = useState(undefined);
|
||||||
|
const [loadingError, setLoadingError] = useState(undefined);
|
||||||
|
const loadAddresses = useCallback(async () => {
|
||||||
|
if (!staticSite && noSelectedAddresses) return;
|
||||||
|
|
||||||
|
let colors = ['#4ab4de'];
|
||||||
|
const dataByGroup = [];
|
||||||
|
const addressGroups = staticSite ? [{addresses: [], name: ""}] : list.filter(group => group.addresses.length);
|
||||||
|
for (const group of addressGroups) {
|
||||||
|
const {data, error} = await fetchJoinAddressByDay(updateRequested, group.addresses, identifier);
|
||||||
|
if (error) {
|
||||||
|
setLoadingError(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
colors = data?.colors;
|
||||||
|
dataByGroup.push({...group, data: data?.join_addresses_by_date || []});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!staticSite) {
|
||||||
|
// First group points from endpoint into frontend based groups
|
||||||
|
const points = {};
|
||||||
|
for (const group of dataByGroup) {
|
||||||
|
const groupName = group.name;
|
||||||
|
for (const point of group.data || []) {
|
||||||
|
if (!points[point.date]) points[point.date] = [];
|
||||||
|
|
||||||
|
const count = point.joinAddresses.map(j => j.count).reduce((partialSum, a) => partialSum + a, 0);
|
||||||
|
points[point.date].push({date: point.date, joinAddresses: [{joinAddress: groupName, count}]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expected output: [{date: number, addresses: [{joinAddress: "name", count: number}]}]
|
||||||
|
const flattened = Object.entries(points)
|
||||||
|
.sort((a, b) => Number(b.date) - Number(a.date))
|
||||||
|
.map(([date, pointList]) => {
|
||||||
|
return {
|
||||||
|
date: Number(date), joinAddresses: pointList.map(point => point.joinAddresses).flat()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setData({
|
||||||
|
join_addresses_by_date: flattened,
|
||||||
|
colors
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// On exported site we get all addresses individually
|
||||||
|
setData({join_addresses_by_date: dataByGroup[0].data, colors})
|
||||||
|
}
|
||||||
|
}, [setData, setLoadingError, identifier, updateRequested, list]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadAddresses();
|
||||||
|
}, [loadAddresses]);
|
||||||
|
|
||||||
if (loadingError) return <ErrorViewCard error={loadingError}/>
|
if (loadingError) return <ErrorViewCard error={loadingError}/>
|
||||||
if (!data) return <CardLoader/>;
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@ -28,8 +84,12 @@ const JoinAddressGraphCard = ({identifier}) => {
|
|||||||
</h6>
|
</h6>
|
||||||
<Toggle value={stack} onValueChange={setStack} color={'amber'}>{t('html.label.stacked')}</Toggle>
|
<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}
|
{data &&
|
||||||
stack={stack}/>
|
<JoinAddressGraph id={'join-address-graph'} data={data?.join_addresses_by_date} colors={data?.colors}
|
||||||
|
stack={stack}/>}
|
||||||
|
{!data && noSelectedAddresses &&
|
||||||
|
<div className="chart-area" style={{height: "450px"}}><p>Select some addresses</p></div>}
|
||||||
|
{!data && !noSelectedAddresses && <ChartLoader/>}
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {useTranslation} from "react-i18next";
|
|
||||||
import {useDataRequest} from "../../../../hooks/dataFetchHook";
|
|
||||||
import {fetchJoinAddressPie} from "../../../../service/serverService";
|
|
||||||
import {ErrorViewCard} from "../../../../views/ErrorView";
|
|
||||||
import {CardLoader} from "../../../navigation/Loader";
|
|
||||||
import {Card} from "react-bootstrap";
|
|
||||||
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
|
||||||
import {faLocationArrow} from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import GroupVisualizer from "../../../graphs/GroupVisualizer";
|
|
||||||
|
|
||||||
const JoinAddressGroupCard = ({identifier}) => {
|
|
||||||
const {t} = useTranslation();
|
|
||||||
|
|
||||||
const {data, loadingError} = useDataRequest(fetchJoinAddressPie, [identifier]);
|
|
||||||
|
|
||||||
if (loadingError) return <ErrorViewCard error={loadingError}/>
|
|
||||||
if (!data) return <CardLoader/>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card id={'join-address-groups'}>
|
|
||||||
<Card.Header>
|
|
||||||
<h6 className="col-black" style={{width: '100%'}}>
|
|
||||||
<Fa icon={faLocationArrow} className="col-amber"/> {t('html.label.latestJoinAddresses')}
|
|
||||||
</h6>
|
|
||||||
</Card.Header>
|
|
||||||
<GroupVisualizer groups={data.slices} colors={data.colors}/>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
export default JoinAddressGroupCard
|
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const MultiSelect = ({options, selectedIndexes, setSelectedIndexes}) => {
|
const MultiSelect = ({options, selectedIndexes, setSelectedIndexes, className}) => {
|
||||||
const handleChange = (event) => {
|
const handleChange = (event) => {
|
||||||
const renderedOptions = Object.values(event.target.selectedOptions)
|
const renderedOptions = Object.values(event.target.selectedOptions)
|
||||||
.map(htmlElement => htmlElement.text)
|
.map(htmlElement => htmlElement.text)
|
||||||
@ -9,7 +9,7 @@ const MultiSelect = ({options, selectedIndexes, setSelectedIndexes}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<select className="form-control" multiple
|
<select className={"form-control " + className} multiple
|
||||||
onChange={handleChange}>
|
onChange={handleChange}>
|
||||||
{options.map((option, i) => {
|
{options.map((option, i) => {
|
||||||
return (
|
return (
|
||||||
|
@ -58,7 +58,7 @@ const Header = ({page, tab, hideUpdater}) => {
|
|||||||
</button>}
|
</button>}
|
||||||
{staticSite && <Fa icon={faClockRotateLeft} title={t('html.label.exported')}/>}
|
{staticSite && <Fa icon={faClockRotateLeft} title={t('html.label.exported')}/>}
|
||||||
{' '}
|
{' '}
|
||||||
<span className="refresh-time"><FormattedDate date={lastUpdate.date}/></span>
|
<span className="refresh-time"><FormattedDate date={lastUpdate.date} react/></span>
|
||||||
</div>
|
</div>
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ export function formatDate(date, offset, pattern, recentDays, recentDaysPattern,
|
|||||||
return date !== 0 ? new SimpleDateFormat(format).format(timestamp) : '-'
|
return date !== 0 ? new SimpleDateFormat(format).format(timestamp) : '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormattedDate = ({date}) => {
|
const FormattedDate = ({date, react}) => {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
const {pattern, recentDays, recentDaysPattern, offset} = useDatePreferences();
|
const {pattern, recentDays, recentDaysPattern, offset} = useDatePreferences();
|
||||||
@ -48,6 +48,12 @@ const FormattedDate = ({date}) => {
|
|||||||
if (!pattern || date === undefined || date === null) return <></>;
|
if (!pattern || date === undefined || date === null) return <></>;
|
||||||
if (!isNumber(date)) return date;
|
if (!isNumber(date)) return date;
|
||||||
|
|
||||||
|
if (react) {
|
||||||
|
return <span title={formatDate(date, offset, pattern, false, null, t)}>
|
||||||
|
{formatDate(date, offset, pattern, recentDays, recentDaysPattern, t)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
return formatDate(date, offset, pattern, recentDays, recentDaysPattern, t);
|
return formatDate(date, offset, pattern, recentDays, recentDaysPattern, t);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
import {createContext, useCallback, useContext, useEffect, useMemo, useState} from "react";
|
||||||
|
import {randomUuid} from "../../util/uuid.js";
|
||||||
|
import {fetchPlayerJoinAddresses} from "../../service/serverService.js";
|
||||||
|
import {useNavigation} from "../navigationHook.jsx";
|
||||||
|
import {usePreferences} from "../preferencesHook.jsx";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
|
const JoinAddressListContext = createContext({});
|
||||||
|
|
||||||
|
export const JoinAddressListContextProvider = ({identifier, children, loadIndividualAddresses, isAllowed}) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const {updateRequested} = useNavigation();
|
||||||
|
const {preferencesLoaded, getKeyedPreference, setSomePreferences} = usePreferences();
|
||||||
|
const [list, setList] = useState([]);
|
||||||
|
|
||||||
|
const updateList = useCallback(newValue => {
|
||||||
|
setList(newValue);
|
||||||
|
const userPreferences = {}
|
||||||
|
userPreferences["join-addresses-" + identifier] = newValue;
|
||||||
|
setSomePreferences(userPreferences);
|
||||||
|
}, [setList])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (preferencesLoaded && !list.length) {
|
||||||
|
const value = getKeyedPreference("join-addresses-" + identifier);
|
||||||
|
if (value?.length) {
|
||||||
|
setList(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [list, setList, preferencesLoaded, getKeyedPreference]);
|
||||||
|
|
||||||
|
const add = useCallback(() => {
|
||||||
|
updateList([...list, {
|
||||||
|
name: t('html.label.addressGroup').replace("{{n}}", list.length + 1),
|
||||||
|
addresses: [],
|
||||||
|
uuid: randomUuid()
|
||||||
|
}])
|
||||||
|
}, [updateList, list]);
|
||||||
|
const remove = useCallback(index => {
|
||||||
|
updateList(list.filter((f, i) => i !== index));
|
||||||
|
}, [updateList, list]);
|
||||||
|
const replace = useCallback((replacement, index) => {
|
||||||
|
const newList = [...list];
|
||||||
|
newList[index] = replacement;
|
||||||
|
updateList(newList)
|
||||||
|
}, [updateList, list]);
|
||||||
|
|
||||||
|
const [allAddresses, setAllAddresses] = useState([]);
|
||||||
|
const [playerAddresses, setPlayerAddresses] = useState(undefined);
|
||||||
|
const loadAddresses = useCallback(async () => {
|
||||||
|
if (!isAllowed) return;
|
||||||
|
const {data, error} = await fetchPlayerJoinAddresses(updateRequested, identifier, !loadIndividualAddresses);
|
||||||
|
setAllAddresses(data?.joinAddresses || [error]);
|
||||||
|
setPlayerAddresses(data?.joinAddressByPlayer);
|
||||||
|
}, [setAllAddresses, identifier, updateRequested, isAllowed]);
|
||||||
|
useEffect(() => {
|
||||||
|
loadAddresses();
|
||||||
|
}, [loadAddresses]);
|
||||||
|
|
||||||
|
const sharedState = useMemo(() => {
|
||||||
|
return {list, add, remove, replace, allAddresses, playerAddresses};
|
||||||
|
}, [list, add, remove, replace, allAddresses, playerAddresses]);
|
||||||
|
return (<JoinAddressListContext.Provider value={sharedState}>
|
||||||
|
{children}
|
||||||
|
</JoinAddressListContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useJoinAddressListContext = () => {
|
||||||
|
return useContext(JoinAddressListContext);
|
||||||
|
}
|
@ -33,7 +33,15 @@ export const PreferencesContextProvider = ({children}) => {
|
|||||||
} else {
|
} else {
|
||||||
localStorage.setItem("preferences", JSON.stringify(withDefaultsRemoved));
|
localStorage.setItem("preferences", JSON.stringify(withDefaultsRemoved));
|
||||||
}
|
}
|
||||||
}, [defaultPreferences, authRequired, authLoaded, loggedIn]);
|
setPreferences({...defaultPreferences, ...(withDefaultsRemoved || {})});
|
||||||
|
}, [defaultPreferences, authRequired, authLoaded, loggedIn, setPreferences]);
|
||||||
|
|
||||||
|
const setSomePreferences = useCallback(userPref => {
|
||||||
|
storePreferences({...preferences, ...userPref});
|
||||||
|
}, [storePreferences, preferences]);
|
||||||
|
const getKeyedPreference = useCallback(key => {
|
||||||
|
return preferences[key];
|
||||||
|
}, [preferences]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updatePreferences();
|
updatePreferences();
|
||||||
@ -42,12 +50,13 @@ export const PreferencesContextProvider = ({children}) => {
|
|||||||
const sharedState = useMemo(() => {
|
const sharedState = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
...preferences,
|
...preferences,
|
||||||
storePreferences,
|
getKeyedPreference,
|
||||||
|
setSomePreferences,
|
||||||
defaultPreferences,
|
defaultPreferences,
|
||||||
preferencesLoaded: Object.keys(defaultPreferences || {}).length > 0
|
preferencesLoaded: Object.keys(defaultPreferences || {}).length > 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[preferences, defaultPreferences, storePreferences]);
|
[preferences, defaultPreferences, storePreferences, getKeyedPreference, setSomePreferences]);
|
||||||
return (<PreferencesContext.Provider value={sharedState}>
|
return (<PreferencesContext.Provider value={sharedState}>
|
||||||
{children}
|
{children}
|
||||||
</PreferencesContext.Provider>
|
</PreferencesContext.Provider>
|
||||||
|
@ -261,42 +261,22 @@ export const fetchPingGraph = async (timestamp, identifier) => {
|
|||||||
return doGetRequest(url, timestamp);
|
return doGetRequest(url, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchJoinAddressPie = async (timestamp, identifier) => {
|
export const fetchJoinAddressByDay = async (timestamp, addresses, identifier) => {
|
||||||
if (identifier) {
|
if (identifier) {
|
||||||
return await fetchJoinAddressPieServer(timestamp, identifier);
|
return await fetchJoinAddressByDayServer(timestamp, addresses, identifier);
|
||||||
} else {
|
} else {
|
||||||
return await fetchJoinAddressPieNetwork(timestamp);
|
return await fetchJoinAddressByDayNetwork(timestamp, addresses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchJoinAddressPieServer = async (timestamp, identifier) => {
|
const fetchJoinAddressByDayServer = async (timestamp, addresses, identifier) => {
|
||||||
let url = `/v1/graph?type=joinAddressPie&server=${identifier}`;
|
let url = `/v1/graph?type=joinAddressByDay&server=${identifier}&addresses=${addresses.join(',')}`;
|
||||||
if (staticSite) url = `/data/graph-joinAddressPie_${identifier}.json`;
|
|
||||||
return doGetRequest(url, timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchJoinAddressPieNetwork = async (timestamp) => {
|
|
||||||
let url = `/v1/graph?type=joinAddressPie`;
|
|
||||||
if (staticSite) url = `/data/graph-joinAddressPie.json`;
|
|
||||||
return doGetRequest(url, timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchJoinAddressByDay = async (timestamp, identifier) => {
|
|
||||||
if (identifier) {
|
|
||||||
return await fetchJoinAddressByDayServer(timestamp, identifier);
|
|
||||||
} else {
|
|
||||||
return await fetchJoinAddressByDayNetwork(timestamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchJoinAddressByDayServer = async (timestamp, identifier) => {
|
|
||||||
let url = `/v1/graph?type=joinAddressByDay&server=${identifier}`;
|
|
||||||
if (staticSite) url = `/data/graph-joinAddressByDay_${identifier}.json`;
|
if (staticSite) url = `/data/graph-joinAddressByDay_${identifier}.json`;
|
||||||
return doGetRequest(url, timestamp);
|
return doGetRequest(url, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchJoinAddressByDayNetwork = async (timestamp) => {
|
const fetchJoinAddressByDayNetwork = async (timestamp, addresses) => {
|
||||||
let url = `/v1/graph?type=joinAddressByDay`;
|
let url = `/v1/graph?type=joinAddressByDay&addresses=${addresses.join(',')}`;
|
||||||
if (staticSite) url = `/data/graph-joinAddressByDay.json`;
|
if (staticSite) url = `/data/graph-joinAddressByDay.json`;
|
||||||
return doGetRequest(url, timestamp);
|
return doGetRequest(url, timestamp);
|
||||||
}
|
}
|
||||||
@ -321,22 +301,22 @@ const fetchNetworkRetentionData = async (timestamp) => {
|
|||||||
return doGetRequest(url, timestamp);
|
return doGetRequest(url, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchPlayerJoinAddresses = async (timestamp, identifier) => {
|
export const fetchPlayerJoinAddresses = async (timestamp, identifier, justList) => {
|
||||||
if (identifier) {
|
if (identifier) {
|
||||||
return await fetchServerPlayerJoinAddresses(timestamp, identifier);
|
return await fetchServerPlayerJoinAddresses(timestamp, identifier, justList);
|
||||||
} else {
|
} else {
|
||||||
return await fetchNetworkPlayerJoinAddresses(timestamp);
|
return await fetchNetworkPlayerJoinAddresses(timestamp, justList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchServerPlayerJoinAddresses = async (timestamp, identifier) => {
|
const fetchServerPlayerJoinAddresses = async (timestamp, identifier, justList) => {
|
||||||
let url = `/v1/joinAddresses?server=${identifier}`;
|
let url = `/v1/joinAddresses?server=${identifier}${justList ? "&listOnly=true" : ""}`;
|
||||||
if (staticSite) url = `/data/joinAddresses-${identifier}.json`;
|
if (staticSite) url = `/data/joinAddresses-${identifier}.json`;
|
||||||
return doGetRequest(url, timestamp);
|
return doGetRequest(url, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchNetworkPlayerJoinAddresses = async (timestamp) => {
|
const fetchNetworkPlayerJoinAddresses = async (timestamp, justList) => {
|
||||||
let url = `/v1/joinAddresses`;
|
let url = `/v1/joinAddresses${justList ? "?listOnly=true" : ""}`;
|
||||||
if (staticSite) url = `/data/joinAddresses.json`;
|
if (staticSite) url = `/data/joinAddresses.json`;
|
||||||
return doGetRequest(url, timestamp);
|
return doGetRequest(url, timestamp);
|
||||||
}
|
}
|
||||||
|
6
Plan/react/dashboard/src/util/uuid.js
Normal file
6
Plan/react/dashboard/src/util/uuid.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// https://stackoverflow.com/a/2117523/20825073
|
||||||
|
export function randomUuid() {
|
||||||
|
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
|
||||||
|
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||||
|
);
|
||||||
|
}
|
@ -1,29 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Col} from "react-bootstrap";
|
import JoinAddresses from "../../components/cards/common/JoinAddresses.jsx";
|
||||||
import JoinAddressGroupCard from "../../components/cards/server/graphs/JoinAddressGroupCard";
|
import {useAuth} from "../../hooks/authenticationHook.jsx";
|
||||||
import JoinAddressGraphCard from "../../components/cards/server/graphs/JoinAddressGraphCard";
|
|
||||||
import LoadIn from "../../components/animation/LoadIn";
|
|
||||||
import ExtendableRow from "../../components/layout/extension/ExtendableRow";
|
|
||||||
import {useAuth} from "../../hooks/authenticationHook";
|
|
||||||
|
|
||||||
const NetworkJoinAddresses = () => {
|
const NetworkJoinAddresses = () => {
|
||||||
const {hasPermission} = useAuth();
|
const {hasPermission} = useAuth();
|
||||||
|
|
||||||
const seeTime = hasPermission('page.network.join.addresses.graphs.time');
|
const seeTime = hasPermission('page.network.join.addresses.graphs.time');
|
||||||
const seeLatest = hasPermission('page.network.join.addresses.graphs.pie');
|
|
||||||
return (
|
return (
|
||||||
<LoadIn>
|
<JoinAddresses id={'network-join-addresses'} identifier={null} seeTime={seeTime}/>
|
||||||
<section className={"network-join-addresses"}>
|
|
||||||
<ExtendableRow id={'row-network-join-addresses-0'}>
|
|
||||||
{seeTime && <Col lg={8}>
|
|
||||||
<JoinAddressGraphCard identifier={undefined}/>
|
|
||||||
</Col>}
|
|
||||||
{seeLatest && <Col lg={4}>
|
|
||||||
<JoinAddressGroupCard identifier={undefined}/>
|
|
||||||
</Col>}
|
|
||||||
</ExtendableRow>
|
|
||||||
</section>
|
|
||||||
</LoadIn>
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,24 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ExtendableRow from "../../components/layout/extension/ExtendableRow";
|
|
||||||
import {Col} from "react-bootstrap";
|
|
||||||
import LoadIn from "../../components/animation/LoadIn";
|
|
||||||
import PlayerRetentionGraphCard from "../../components/cards/common/PlayerRetentionGraphCard";
|
|
||||||
import {useAuth} from "../../hooks/authenticationHook";
|
import {useAuth} from "../../hooks/authenticationHook";
|
||||||
|
import PlayerRetention from "../../components/cards/common/PlayerRetention.jsx";
|
||||||
|
|
||||||
const NetworkPlayerRetention = () => {
|
const NetworkPlayerRetention = () => {
|
||||||
const {hasPermission} = useAuth();
|
const {hasPermission} = useAuth();
|
||||||
|
|
||||||
const seeRetention = hasPermission('page.network.retention');
|
const seeRetention = hasPermission('page.network.retention');
|
||||||
return (
|
return (
|
||||||
<LoadIn>
|
<PlayerRetention id={"network-retention"} identifier={null} seeRetention={seeRetention}/>
|
||||||
{seeRetention && <section className="network-retention">
|
|
||||||
<ExtendableRow id={'row-network-retention-0'}>
|
|
||||||
<Col lg={12}>
|
|
||||||
<PlayerRetentionGraphCard identifier={null}/>
|
|
||||||
</Col>
|
|
||||||
</ExtendableRow>
|
|
||||||
</section>}
|
|
||||||
</LoadIn>
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,31 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Col} from "react-bootstrap";
|
|
||||||
import JoinAddressGroupCard from "../../components/cards/server/graphs/JoinAddressGroupCard";
|
|
||||||
import JoinAddressGraphCard from "../../components/cards/server/graphs/JoinAddressGraphCard";
|
|
||||||
import {useParams} from "react-router-dom";
|
import {useParams} from "react-router-dom";
|
||||||
import LoadIn from "../../components/animation/LoadIn";
|
import JoinAddresses from "../../components/cards/common/JoinAddresses.jsx";
|
||||||
import ExtendableRow from "../../components/layout/extension/ExtendableRow";
|
import {useAuth} from "../../hooks/authenticationHook.jsx";
|
||||||
import {useAuth} from "../../hooks/authenticationHook";
|
|
||||||
|
|
||||||
const ServerJoinAddresses = () => {
|
const ServerJoinAddresses = () => {
|
||||||
const {hasPermission} = useAuth();
|
|
||||||
const {identifier} = useParams();
|
const {identifier} = useParams();
|
||||||
|
const {hasPermission} = useAuth();
|
||||||
const seeTime = hasPermission('page.server.join.addresses.graphs.time');
|
const seeTime = hasPermission('page.server.join.addresses.graphs.time');
|
||||||
const seeLatest = hasPermission('page.server.join.addresses.graphs.pie');
|
|
||||||
return (
|
return (
|
||||||
<LoadIn>
|
<JoinAddresses id={'server-join-addresses'} identifier={identifier} seeTime={seeTime}/>
|
||||||
<section className={"server-join-addresses"}>
|
|
||||||
<ExtendableRow id={'row-server-join-addresses-0'}>
|
|
||||||
{seeTime && <Col lg={8}>
|
|
||||||
<JoinAddressGraphCard identifier={identifier}/>
|
|
||||||
</Col>}
|
|
||||||
{seeLatest && <Col lg={4}>
|
|
||||||
<JoinAddressGroupCard identifier={identifier}/>
|
|
||||||
</Col>}
|
|
||||||
</ExtendableRow>
|
|
||||||
</section>
|
|
||||||
</LoadIn>
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ExtendableRow from "../../components/layout/extension/ExtendableRow";
|
|
||||||
import {Col} from "react-bootstrap";
|
|
||||||
import LoadIn from "../../components/animation/LoadIn";
|
|
||||||
import PlayerRetentionGraphCard from "../../components/cards/common/PlayerRetentionGraphCard";
|
|
||||||
import {useParams} from "react-router-dom";
|
import {useParams} from "react-router-dom";
|
||||||
import {useAuth} from "../../hooks/authenticationHook";
|
import {useAuth} from "../../hooks/authenticationHook";
|
||||||
|
import PlayerRetention from "../../components/cards/common/PlayerRetention.jsx";
|
||||||
|
|
||||||
const ServerPlayerRetention = () => {
|
const ServerPlayerRetention = () => {
|
||||||
const {hasPermission} = useAuth();
|
const {hasPermission} = useAuth();
|
||||||
@ -12,15 +9,7 @@ const ServerPlayerRetention = () => {
|
|||||||
|
|
||||||
const seeRetention = hasPermission('page.server.retention');
|
const seeRetention = hasPermission('page.server.retention');
|
||||||
return (
|
return (
|
||||||
<LoadIn>
|
<PlayerRetention id={"server-retention"} identifier={identifier} seeRetention={seeRetention}/>
|
||||||
<section className="server-retention">
|
|
||||||
{seeRetention && <ExtendableRow id={'row-server-retention-0'}>
|
|
||||||
<Col lg={12}>
|
|
||||||
<PlayerRetentionGraphCard identifier={identifier}/>
|
|
||||||
</Col>
|
|
||||||
</ExtendableRow>}
|
|
||||||
</section>
|
|
||||||
</LoadIn>
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user