mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-02-03 14:01:47 +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;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Object that has a value tied to a date.
|
||||
*
|
||||
@ -39,4 +41,25 @@ public class DateObj<T> implements DateHolder {
|
||||
public T getValue() {
|
||||
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 org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
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_JOIN_ADDRESSES("See Join Addresses -tab"),
|
||||
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_RETENTION("See Player Retention -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_JOIN_ADDRESSES("See Join Addresses -tab"),
|
||||
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_RETENTION("See Player Retention -tab"),
|
||||
PAGE_SERVER_GEOLOCATIONS("See Geolocations tab"),
|
||||
@ -156,4 +161,23 @@ public enum WebPermission implements Supplier<String>, Lang {
|
||||
public String getDefault() {
|
||||
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=hourlyUniqueAndNew",
|
||||
"graph?type=serverPie",
|
||||
"graph?type=joinAddressPie",
|
||||
"graph?type=joinAddressByDay",
|
||||
"graph?type=activity",
|
||||
"graph?type=geolocation",
|
||||
|
@ -137,7 +137,6 @@ public class ServerPageExporter extends FileExporter {
|
||||
"graph?type=geolocation&server=" + serverUUID,
|
||||
"graph?type=uniqueAndNew&server=" + serverUUID,
|
||||
"graph?type=hourlyUniqueAndNew&server=" + serverUUID,
|
||||
"graph?type=joinAddressPie&server=" + serverUUID,
|
||||
"graph?type=joinAddressByDay&server=" + serverUUID,
|
||||
"graph?type=serverCalendar&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.RetentionData;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.PlayerJoinAddresses;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;
|
||||
import com.djrapitops.plan.delivery.domain.mutators.PlayerKillMutator;
|
||||
import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator;
|
||||
@ -160,14 +161,25 @@ public class JSONFactory {
|
||||
return db.query(PlayerRetentionQueries.fetchRetentionData());
|
||||
}
|
||||
|
||||
public Map<UUID, String> playerJoinAddresses(ServerUUID serverUUID) {
|
||||
public PlayerJoinAddresses playerJoinAddresses(ServerUUID serverUUID, boolean includeByPlayerMap) {
|
||||
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();
|
||||
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) {
|
||||
|
@ -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.Point;
|
||||
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.special.WorldMap;
|
||||
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.sql.tables.JoinAddressTable;
|
||||
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.Maps;
|
||||
import net.playeranalytics.plugin.scheduling.TimeAmount;
|
||||
@ -457,34 +456,6 @@ public class GraphJSONCreator {
|
||||
.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) {
|
||||
Integer unknown = joinAddresses.get(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
public Pie joinAddressPie(Map<String, Integer> joinAddresses) {
|
||||
return new JoinAddressPie(joinAddresses);
|
||||
}
|
||||
|
||||
public WorldPie worldPie(WorldTimes worldTimes) {
|
||||
WorldAliasSettings worldAliasSettings = config.getWorldAliasSettings();
|
||||
Map<String, Long> playtimePerAlias = worldAliasSettings.getPlaytimePerAlias(worldTimes);
|
||||
|
@ -42,7 +42,6 @@ public enum DataID {
|
||||
GRAPH_ACTIVITY,
|
||||
GRAPH_PING,
|
||||
GRAPH_SERVER_PIE,
|
||||
GRAPH_HOSTNAME_PIE,
|
||||
GRAPH_PUNCHCARD,
|
||||
SERVER_OVERVIEW,
|
||||
ONLINE_OVERVIEW,
|
||||
@ -54,12 +53,26 @@ public enum DataID {
|
||||
EXTENSION_TABS,
|
||||
EXTENSION_JSON,
|
||||
LIST_SERVERS,
|
||||
JOIN_ADDRESSES_BY_DAY,
|
||||
JOIN_ADDRESSES_BY_DAY(false),
|
||||
PLAYER_RETENTION,
|
||||
PLAYER_JOIN_ADDRESSES,
|
||||
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) {
|
||||
if (serverUUID == null) return name();
|
||||
return name() + "_" + serverUUID;
|
||||
|
@ -74,6 +74,10 @@ public interface JSONStorage extends SubSystem {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public static StoredJSON fromObject(Object json, long timestamp) {
|
||||
return new StoredJSON(new Gson().toJson(json), timestamp);
|
||||
}
|
||||
|
||||
public String getJson() {
|
||||
return json;
|
||||
}
|
||||
|
@ -39,11 +39,16 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Resolves /v1/graph JSON requests.
|
||||
@ -117,7 +122,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
||||
@ExampleObject("aggregatedPing"),
|
||||
@ExampleObject("punchCard"),
|
||||
@ExampleObject("serverPie"),
|
||||
@ExampleObject("joinAddressPie"),
|
||||
@ExampleObject("joinAddressByDay"),
|
||||
}),
|
||||
@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;
|
||||
if (request.getQuery().get("server").isPresent()) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
|
||||
storedJSON = jsonResolverService.resolve(
|
||||
timestamp, dataID, serverUUID,
|
||||
theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery())
|
||||
);
|
||||
Function<ServerUUID, Object> generationFunction = theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery());
|
||||
if (dataID.isCacheable()) {
|
||||
storedJSON = jsonResolverService.resolve(timestamp, dataID, serverUUID, generationFunction);
|
||||
} else {
|
||||
storedJSON = JSONStorage.StoredJSON.fromObject(generationFunction.apply(serverUUID), System.currentTimeMillis());
|
||||
}
|
||||
} else {
|
||||
// Assume network
|
||||
storedJSON = jsonResolverService.resolve(
|
||||
timestamp, dataID, () -> generateGraphDataJSONOfType(dataID, request.getQuery())
|
||||
);
|
||||
Supplier<Object> generationFunction = () -> generateGraphDataJSONOfType(dataID, request.getQuery());
|
||||
if (dataID.isCacheable()) {
|
||||
storedJSON = jsonResolverService.resolve(
|
||||
timestamp, dataID, generationFunction
|
||||
);
|
||||
} else {
|
||||
storedJSON = JSONStorage.StoredJSON.fromObject(generationFunction.get(), System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
return storedJSON;
|
||||
}
|
||||
@ -197,8 +208,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
||||
return DataID.GRAPH_PUNCHCARD;
|
||||
case "serverPie":
|
||||
return DataID.GRAPH_SERVER_PIE;
|
||||
case "joinAddressPie":
|
||||
return DataID.GRAPH_HOSTNAME_PIE;
|
||||
case "joinAddressByDay":
|
||||
return DataID.JOIN_ADDRESSES_BY_DAY;
|
||||
default:
|
||||
@ -229,8 +238,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
||||
return List.of(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS);
|
||||
case GRAPH_WORLD_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:
|
||||
return List.of(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME);
|
||||
default:
|
||||
@ -258,8 +265,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
||||
return List.of(WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP);
|
||||
case GRAPH_ONLINE_PROXIES:
|
||||
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:
|
||||
return List.of(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME);
|
||||
default:
|
||||
@ -283,8 +288,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
||||
return graphJSON.serverCalendarJSON(serverUUID);
|
||||
case GRAPH_WORLD_PIE:
|
||||
return graphJSON.serverWorldPieJSONAsMap(serverUUID);
|
||||
case GRAPH_HOSTNAME_PIE:
|
||||
return graphJSON.playerHostnamePieJSONAsMap(serverUUID);
|
||||
case GRAPH_ACTIVITY:
|
||||
return graphJSON.activityGraphsJSONAsMap(serverUUID);
|
||||
case GRAPH_WORLD_MAP:
|
||||
@ -294,19 +297,24 @@ public class GraphsJSONResolver extends JSONResolver {
|
||||
case GRAPH_PUNCHCARD:
|
||||
return graphJSON.punchCardJSONAsMap(serverUUID);
|
||||
case JOIN_ADDRESSES_BY_DAY:
|
||||
try {
|
||||
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)");
|
||||
}
|
||||
return joinAddressGraph(serverUUID, query);
|
||||
default:
|
||||
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) {
|
||||
switch (id) {
|
||||
case GRAPH_ACTIVITY:
|
||||
@ -319,23 +327,26 @@ public class GraphsJSONResolver extends JSONResolver {
|
||||
return graphJSON.networkCalendarJSON();
|
||||
case GRAPH_SERVER_PIE:
|
||||
return graphJSON.serverPreferencePieJSONAsMap();
|
||||
case GRAPH_HOSTNAME_PIE:
|
||||
return graphJSON.playerHostnamePieJSONAsMap();
|
||||
case GRAPH_WORLD_MAP:
|
||||
return graphJSON.geolocationGraphsJSONAsMap();
|
||||
case GRAPH_ONLINE_PROXIES:
|
||||
return graphJSON.proxyPlayersOnlineGraphs();
|
||||
case JOIN_ADDRESSES_BY_DAY:
|
||||
try {
|
||||
return graphJSON.joinAddressesByDay(
|
||||
query.get("after").map(Long::parseLong).orElse(0L),
|
||||
query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis())
|
||||
);
|
||||
} catch (@Untrusted NumberFormatException e) {
|
||||
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
|
||||
}
|
||||
return joinAddressGraph(query);
|
||||
default:
|
||||
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;
|
||||
|
||||
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.rendering.json.JSONFactory;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
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.URIQuery;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
|
||||
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.media.Content;
|
||||
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.responses.ApiResponse;
|
||||
import jakarta.ws.rs.GET;
|
||||
@ -42,7 +45,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@ -69,17 +71,27 @@ public class PlayerJoinAddressJSONResolver extends JSONResolver {
|
||||
@Override
|
||||
public boolean canAccess(@Untrusted Request request) {
|
||||
WebUser user = request.getUser().orElse(new WebUser(""));
|
||||
if (request.getQuery().get("server").isPresent()) {
|
||||
return user.hasPermission(WebPermission.PAGE_SERVER_RETENTION);
|
||||
@Untrusted URIQuery query = request.getQuery();
|
||||
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
|
||||
@Operation(
|
||||
description = "Get join address information of players for server or network",
|
||||
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")
|
||||
},
|
||||
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()) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request);
|
||||
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
|
||||
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 java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Endpoint for getting list of available Plan web permissions.
|
||||
@ -75,7 +77,10 @@ public class WebPermissionJSONResolver implements Resolver {
|
||||
}
|
||||
|
||||
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);
|
||||
return Response.builder()
|
||||
|
@ -108,7 +108,7 @@ public class LocaleSystem implements SubSystem {
|
||||
HtmlLang.values(),
|
||||
JSLang.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_NETWORK_CALENDAR("html.label.networkCalendar", "Network Calendar"),
|
||||
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_KDR("html.label.kdr", "KDR"),
|
||||
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.UsersTable;
|
||||
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.ResultSet;
|
||||
@ -43,6 +45,7 @@ public class JoinAddressQueries {
|
||||
/* Static method class */
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static Query<Map<String, Integer>> latestJoinAddresses() {
|
||||
String selectLatestJoinAddresses = SELECT +
|
||||
"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));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static Query<Map<String, Integer>> latestJoinAddresses(ServerUUID serverUUID) {
|
||||
String selectLatestSessionStarts = SELECT + SessionsTable.USER_ID + ",MAX(" + SessionsTable.SESSION_START + ") as max_start" +
|
||||
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() {
|
||||
return db -> {
|
||||
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) {
|
||||
String sql = SELECT + DISTINCT + SessionsTable.USER_ID +
|
||||
FROM + JoinAddressTable.TABLE_NAME + " j" +
|
||||
@ -162,21 +198,27 @@ public class JoinAddressQueries {
|
||||
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 -> {
|
||||
Sql sql = db.getSql();
|
||||
|
||||
List<Integer> ids = db.query(joinAddressIds(addressFilter));
|
||||
if (ids != null && ids.isEmpty()) return List.of();
|
||||
|
||||
String selectAddresses = SELECT +
|
||||
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
|
||||
"*1000 as date," +
|
||||
JoinAddressTable.JOIN_ADDRESS +
|
||||
JoinAddressTable.JOIN_ADDRESS + ',' +
|
||||
SessionsTable.USER_ID +
|
||||
", COUNT(1) as count" +
|
||||
FROM + SessionsTable.TABLE_NAME + " s" +
|
||||
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
|
||||
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
||||
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) {
|
||||
@Override
|
||||
@ -193,9 +235,9 @@ public class JoinAddressQueries {
|
||||
while (set.next()) {
|
||||
long date = set.getLong("date");
|
||||
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
|
||||
int count = set.getInt("count");
|
||||
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
|
||||
joinAddresses.put(joinAddress, count);
|
||||
// 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()
|
||||
@ -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 -> {
|
||||
Sql sql = db.getSql();
|
||||
|
||||
List<Integer> ids = db.query(joinAddressIds(addressFilter));
|
||||
if (ids != null && ids.isEmpty()) return List.of();
|
||||
|
||||
String selectAddresses = SELECT +
|
||||
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
|
||||
"*1000 as date," +
|
||||
JoinAddressTable.JOIN_ADDRESS +
|
||||
JoinAddressTable.JOIN_ADDRESS + ',' +
|
||||
SessionsTable.USER_ID +
|
||||
", COUNT(1) as count" +
|
||||
FROM + SessionsTable.TABLE_NAME + " s" +
|
||||
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
|
||||
WHERE + SessionsTable.SESSION_START + ">?" +
|
||||
AND + SessionsTable.SESSION_START + "<=?" +
|
||||
GROUP_BY + "date,j." + JoinAddressTable.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) {
|
||||
@Override
|
||||
@ -235,9 +294,9 @@ public class JoinAddressQueries {
|
||||
while (set.next()) {
|
||||
long date = set.getLong("date");
|
||||
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
|
||||
int count = set.getInt("count");
|
||||
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
|
||||
joinAddresses.put(joinAddress, count);
|
||||
// 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()
|
||||
|
@ -39,7 +39,7 @@ public class UpdateWebPermissionsPatch extends Patch {
|
||||
|
||||
@Override
|
||||
public boolean hasBeenApplied() {
|
||||
List<String> defaultPermissions = Arrays.stream(WebPermission.values())
|
||||
List<String> defaultPermissions = Arrays.stream(WebPermission.nonDeprecatedValues())
|
||||
.map(WebPermission::getPermission)
|
||||
.collect(Collectors.toList());
|
||||
List<String> storedPermissions = query(WebUserQueries.fetchAvailablePermissions());
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "活跃"
|
||||
activePlaytime: "活跃时间"
|
||||
activityIndex: "活跃指数"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "挂机"
|
||||
afkTime: "挂机时间"
|
||||
all: "全部"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "查看按国家划分的Ping表"
|
||||
page_network_join_addresses: "查看加入地址 - 选项卡"
|
||||
page_network_join_addresses_graphs: "查看加入地址图表"
|
||||
page_network_join_addresses_graphs_pie: "查看最新加入地址图表"
|
||||
page_network_join_addresses_graphs_time: "查看加入地址随时间变化的图表"
|
||||
page_network_overview: "查看网络总览 - 选项卡"
|
||||
page_network_overview_graphs: "查看网络总览图表"
|
||||
@ -700,7 +701,6 @@ html:
|
||||
page_server_geolocations_ping_per_country: "查看按国家划分的延迟表"
|
||||
page_server_join_addresses: "查看服务器加入地址 - 选项卡"
|
||||
page_server_join_addresses_graphs: "查看服务器加入地址图表"
|
||||
page_server_join_addresses_graphs_pie: "查看最新加入地址图表"
|
||||
page_server_join_addresses_graphs_time: "查看服务器加入地址随时间变化的图表"
|
||||
page_server_online_activity: "查看在线活动 - 选项卡"
|
||||
page_server_online_activity_graphs: "查看在线活动图表"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Aktivní"
|
||||
activePlaytime: "Aktivní herní čas"
|
||||
activityIndex: "Index aktivity"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "AFK čas"
|
||||
all: "Vše"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Aktiv"
|
||||
activePlaytime: "Aktive Spielzeit"
|
||||
activityIndex: "Aktivitätsindex"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "AFK Zeit"
|
||||
all: "Gesamt"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Active"
|
||||
activePlaytime: "Active Playtime"
|
||||
activityIndex: "Activity Index"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "AFK Time"
|
||||
all: "All"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Activo"
|
||||
activePlaytime: "Tiempo de juego activo"
|
||||
activityIndex: "Índice de actividad"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "Tiempo AFK"
|
||||
all: "Todo"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Aktiivinen"
|
||||
activePlaytime: "Aktiivinen peliaika"
|
||||
activityIndex: "Aktiivisuus Indeksi"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "Aika AFK:ina"
|
||||
all: "Kaikki"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
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_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_overview: "Näkee Verkoston katsaus osion"
|
||||
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_join_addresses: "Näkee Liittymäosoitteet osion"
|
||||
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_online_activity: "Näkee Online Aktiivisuus osion"
|
||||
page_server_online_activity_graphs: "Näkee Online Aktiivisuus kaaviot"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Actif"
|
||||
activePlaytime: "Temps Actif"
|
||||
activityIndex: "Indice d'Activité"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "Temps AFK"
|
||||
all: "Tout"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
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_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_overview: "Voir Aperçu du réseau -tab"
|
||||
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_retention: "Voir l'onglet Rétention des joueurs"
|
||||
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_overview: "Voir les perspectives de la session"
|
||||
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_overview: "Voir l'aperçu des joueurs -tab"
|
||||
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_versus: "Voir PvP & PvE -tab"
|
||||
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_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_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_online_activity: "Voir l'activité en ligne -tab"
|
||||
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_plugins: "Voir les onglets Plugins des serveurs"
|
||||
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_overview: "Voir les perspectives de la session"
|
||||
page_server_sessions_world_pie: "Voir le graphique de la carte du monde"
|
||||
@ -794,6 +794,7 @@ html:
|
||||
generic:
|
||||
are: "`sont`"
|
||||
label:
|
||||
editQuery: "Edit Query"
|
||||
from: ">de</label>"
|
||||
makeAnother: "Faire une autre Requête"
|
||||
servers:
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Attivo"
|
||||
activePlaytime: "Active Playtime"
|
||||
activityIndex: "Indice Inattività"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "Tempo AFK"
|
||||
all: "Tutto"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "よくログインしている"
|
||||
activePlaytime: "アクティブなプレイ時間"
|
||||
activityIndex: "活動指数"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "離席"
|
||||
afkTime: "離席時間"
|
||||
all: "全て"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "国ごとのPing表を表示"
|
||||
page_network_join_addresses: "参加アドレスタブを表示"
|
||||
page_network_join_addresses_graphs: "参加アドレスのグラフを表示"
|
||||
page_network_join_addresses_graphs_pie: "最後に参加したアドレスのグラフを表示"
|
||||
page_network_join_addresses_graphs_time: "参加アドレスの経時変化のグラフを表示"
|
||||
page_network_overview: "ネットワークの概要タブを表示"
|
||||
page_network_overview_graphs: "ネットワークの概要グラフを表示"
|
||||
@ -700,7 +701,6 @@ html:
|
||||
page_server_geolocations_ping_per_country: "国ごとのPing表を表示"
|
||||
page_server_join_addresses: "参加アドレスタブを表示"
|
||||
page_server_join_addresses_graphs: "参加アドレスグラフを表示"
|
||||
page_server_join_addresses_graphs_pie: "最後に参加したアドレスのグラフを表示"
|
||||
page_server_join_addresses_graphs_time: "参加アドレスの経時変化のグラフを表示"
|
||||
page_server_online_activity: "オンラインアクティビティタブを表示"
|
||||
page_server_online_activity_graphs: "オンラインアクティビティグラフを表示"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "활동적인"
|
||||
activePlaytime: "Active Playtime"
|
||||
activityIndex: "활동 색인"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "AFK 시간"
|
||||
all: "모두"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Actief"
|
||||
activePlaytime: "Actieve Speeltijd"
|
||||
activityIndex: "Activiteitsindex"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "AFK Tijd"
|
||||
all: "Alle"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Ativo"
|
||||
activePlaytime: "Active Playtime"
|
||||
activityIndex: "Índice de Atividade"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "AFK Time"
|
||||
all: "Todos"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Активный"
|
||||
activePlaytime: "Активное время игры"
|
||||
activityIndex: "Индекс активности"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "Время AFK"
|
||||
all: "Все"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Aktivite"
|
||||
activePlaytime: "Aktif Oyun Süresi"
|
||||
activityIndex: "Aktivite göstergesi"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "AFK Süresi"
|
||||
all: "Tamamı"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "Активний"
|
||||
activePlaytime: "Активний час гри"
|
||||
activityIndex: "Індекс активності"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "Час AFK"
|
||||
all: "Всі"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -290,6 +290,8 @@ html:
|
||||
active: "活躍"
|
||||
activePlaytime: "活躍時間"
|
||||
activityIndex: "活躍指數"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "掛機"
|
||||
afkTime: "掛機時間"
|
||||
all: "全部"
|
||||
@ -664,7 +666,6 @@ html:
|
||||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
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_overview: "See Network Overview -tab"
|
||||
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_join_addresses: "See Join Addresses -tab"
|
||||
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_online_activity: "See Online Activity -tab"
|
||||
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=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=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=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),
|
||||
@ -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/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/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 + "&listOnly=true", WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME, 200, 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/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=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=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=geolocation", WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP, 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_GRAPHS, "playerbase-graph", "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_PIE, "join-address-groups", "join-addresses"),
|
||||
Arguments.arguments(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME, "server-join-addresses", "join-addresses"),
|
||||
Arguments.arguments(WebPermission.PAGE_SERVER_RETENTION, "retention-graph", "retention"),
|
||||
Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERS, "players-table", "players"),
|
||||
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_GRAPHS, "playerbase-graph", "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_PIE, "join-address-groups", "join-addresses"),
|
||||
Arguments.arguments(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME, "network-join-addresses", "join-addresses"),
|
||||
Arguments.arguments(WebPermission.PAGE_NETWORK_RETENTION, "retention-graph", "retention"),
|
||||
Arguments.arguments(WebPermission.PAGE_NETWORK_PLAYERS, "players-table", "players"),
|
||||
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> result = database.query(JoinAddressQueries.allJoinAddresses());
|
||||
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;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.DateObj;
|
||||
import com.djrapitops.plan.gathering.domain.FinishedSession;
|
||||
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.storage.database.DatabaseTestPreparer;
|
||||
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.events.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import utilities.RandomData;
|
||||
import utilities.TestConstants;
|
||||
import utilities.TestData;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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
|
||||
default void joinAddressIsTruncated() {
|
||||
db().executeTransaction(new StoreWorldNameTransaction(serverUUID(), worlds[0]));
|
||||
@ -201,15 +254,6 @@ public interface JoinAddressQueriesTest extends DatabaseTestPreparer {
|
||||
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
|
||||
default void joinAddressQueryHasDistinctPlayers() {
|
||||
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 {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
||||
import {faQuestionCircle} from "@fortawesome/free-regular-svg-icons";
|
||||
import {useJoinAddressListContext} from "../../../hooks/context/joinAddressListContextHook.jsx";
|
||||
|
||||
const dayMs = 24 * 3600000;
|
||||
const getWeek = (date) => {
|
||||
@ -26,7 +27,7 @@ const getWeek = (date) => {
|
||||
return Math.ceil(dayOfYear / 7)
|
||||
};
|
||||
|
||||
const PlayerRetentionGraphCard = ({identifier}) => {
|
||||
const PlayerRetentionGraphCard = ({identifier, selectedGroupBy, setSelectedGroupBy}) => {
|
||||
const {t} = useTranslation();
|
||||
const {nightModeEnabled} = useTheme();
|
||||
const {setHelpModalTopic} = useNavigation();
|
||||
@ -40,6 +41,8 @@ const PlayerRetentionGraphCard = ({identifier}) => {
|
||||
loadingError: joinAddressLoadingError
|
||||
} = useDataRequest(fetchPlayerJoinAddresses, [identifier]);
|
||||
|
||||
const {list, playerAddresses} = useJoinAddressListContext();
|
||||
|
||||
const [selectedWindow, setSelectedWindow] = useState('days');
|
||||
const windowOptions = useMemo(() => [
|
||||
{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-ever', displayName: t('html.label.retention.inAnytime'), start: 0},
|
||||
], [t, time]);
|
||||
const [selectedGroupBy, setSelectedGroupBy] = useState('none');
|
||||
// State moved to higher level for join address group selection
|
||||
const groupByOptions = useMemo(() => [
|
||||
{name: 'none', displayName: t('html.label.retention.groupByNone')},
|
||||
{name: 'days', displayName: t('html.label.time.day')},
|
||||
@ -165,8 +168,11 @@ const PlayerRetentionGraphCard = ({identifier}) => {
|
||||
break;
|
||||
case 'joinAddress':
|
||||
const joinAddress = joinAddressData[point.playerUUID];
|
||||
if (!grouped[joinAddress]) grouped[joinAddress] = [];
|
||||
grouped[joinAddress].push(point);
|
||||
const joinAddressGroups = list.filter(g => g.addresses.includes(joinAddress)).map(g => g.name);
|
||||
for (const joinAddressGroup of joinAddressGroups) {
|
||||
if (!grouped[joinAddressGroup]) grouped[joinAddressGroup] = [];
|
||||
grouped[joinAddressGroup].push(point);
|
||||
}
|
||||
break;
|
||||
case 'none':
|
||||
default:
|
||||
@ -175,7 +181,7 @@ const PlayerRetentionGraphCard = ({identifier}) => {
|
||||
}
|
||||
}
|
||||
return grouped;
|
||||
}, [groupByOptions, selectedGroupBy]);
|
||||
}, [groupByOptions, selectedGroupBy, list]);
|
||||
|
||||
const createSeries = useCallback(async (retentionData, joinAddressData) => {
|
||||
|
||||
@ -207,10 +213,10 @@ const PlayerRetentionGraphCard = ({identifier}) => {
|
||||
}, [nightModeEnabled, mapToData, groupOptions, selectedGroup, selectedYAxis, group]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || !joinAddressData) return;
|
||||
if (!data || !playerAddresses) return;
|
||||
|
||||
createSeries(data.player_retention, joinAddressData.join_address_by_player).then(series => setSeries(series.flat()));
|
||||
}, [data, joinAddressData, createSeries, setSeries]);
|
||||
createSeries(data.player_retention, playerAddresses).then(series => setSeries(series.flat()));
|
||||
}, [data, playerAddresses, createSeries, setSeries]);
|
||||
|
||||
useEffect(() => {
|
||||
const windowName = windowOptions.find(option => option.name === selectedWindow).displayName;
|
||||
@ -261,13 +267,15 @@ const PlayerRetentionGraphCard = ({identifier}) => {
|
||||
},
|
||||
tooltip: selectedAxis === 'date' || selectedAxis === 'deltas' ? {
|
||||
enabled: true,
|
||||
shared: series.length <= 10,
|
||||
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,
|
||||
shared: series.length <= 10,
|
||||
valueDecimals: 2,
|
||||
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
|
||||
})
|
||||
|
@ -1,24 +1,80 @@
|
||||
import React, {useState} from 'react';
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {useDataRequest} from "../../../../hooks/dataFetchHook";
|
||||
import {fetchJoinAddressByDay} from "../../../../service/serverService";
|
||||
import {ErrorViewCard} from "../../../../views/ErrorView";
|
||||
import {CardLoader} from "../../../navigation/Loader";
|
||||
import {ChartLoader} from "../../../navigation/Loader";
|
||||
import {Card} from "react-bootstrap";
|
||||
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
||||
import {faChartColumn} from "@fortawesome/free-solid-svg-icons";
|
||||
import JoinAddressGraph from "../../../graphs/JoinAddressGraph";
|
||||
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 {t} = useTranslation();
|
||||
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 (!data) return <CardLoader/>;
|
||||
|
||||
|
||||
return (
|
||||
<Card>
|
||||
@ -28,8 +84,12 @@ const JoinAddressGraphCard = ({identifier}) => {
|
||||
</h6>
|
||||
<Toggle value={stack} onValueChange={setStack} color={'amber'}>{t('html.label.stacked')}</Toggle>
|
||||
</Card.Header>
|
||||
<JoinAddressGraph id={'join-address-graph'} data={data?.join_addresses_by_date} colors={data?.colors}
|
||||
stack={stack}/>
|
||||
{data &&
|
||||
<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>
|
||||
)
|
||||
};
|
||||
|
@ -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';
|
||||
|
||||
const MultiSelect = ({options, selectedIndexes, setSelectedIndexes}) => {
|
||||
const MultiSelect = ({options, selectedIndexes, setSelectedIndexes, className}) => {
|
||||
const handleChange = (event) => {
|
||||
const renderedOptions = Object.values(event.target.selectedOptions)
|
||||
.map(htmlElement => htmlElement.text)
|
||||
@ -9,7 +9,7 @@ const MultiSelect = ({options, selectedIndexes, setSelectedIndexes}) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<select className="form-control" multiple
|
||||
<select className={"form-control " + className} multiple
|
||||
onChange={handleChange}>
|
||||
{options.map((option, i) => {
|
||||
return (
|
||||
|
@ -58,7 +58,7 @@ const Header = ({page, tab, hideUpdater}) => {
|
||||
</button>}
|
||||
{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>
|
||||
</>}
|
||||
|
||||
|
@ -40,7 +40,7 @@ export function formatDate(date, offset, pattern, recentDays, recentDaysPattern,
|
||||
return date !== 0 ? new SimpleDateFormat(format).format(timestamp) : '-'
|
||||
}
|
||||
|
||||
const FormattedDate = ({date}) => {
|
||||
const FormattedDate = ({date, react}) => {
|
||||
const {t} = useTranslation();
|
||||
|
||||
const {pattern, recentDays, recentDaysPattern, offset} = useDatePreferences();
|
||||
@ -48,6 +48,12 @@ const FormattedDate = ({date}) => {
|
||||
if (!pattern || date === undefined || date === null) return <></>;
|
||||
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);
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
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(() => {
|
||||
updatePreferences();
|
||||
@ -42,12 +50,13 @@ export const PreferencesContextProvider = ({children}) => {
|
||||
const sharedState = useMemo(() => {
|
||||
return {
|
||||
...preferences,
|
||||
storePreferences,
|
||||
getKeyedPreference,
|
||||
setSomePreferences,
|
||||
defaultPreferences,
|
||||
preferencesLoaded: Object.keys(defaultPreferences || {}).length > 0
|
||||
}
|
||||
},
|
||||
[preferences, defaultPreferences, storePreferences]);
|
||||
[preferences, defaultPreferences, storePreferences, getKeyedPreference, setSomePreferences]);
|
||||
return (<PreferencesContext.Provider value={sharedState}>
|
||||
{children}
|
||||
</PreferencesContext.Provider>
|
||||
|
@ -261,42 +261,22 @@ export const fetchPingGraph = async (timestamp, identifier) => {
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchJoinAddressPie = async (timestamp, identifier) => {
|
||||
export const fetchJoinAddressByDay = async (timestamp, addresses, identifier) => {
|
||||
if (identifier) {
|
||||
return await fetchJoinAddressPieServer(timestamp, identifier);
|
||||
return await fetchJoinAddressByDayServer(timestamp, addresses, identifier);
|
||||
} else {
|
||||
return await fetchJoinAddressPieNetwork(timestamp);
|
||||
return await fetchJoinAddressByDayNetwork(timestamp, addresses);
|
||||
}
|
||||
}
|
||||
|
||||
const fetchJoinAddressPieServer = async (timestamp, identifier) => {
|
||||
let url = `/v1/graph?type=joinAddressPie&server=${identifier}`;
|
||||
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}`;
|
||||
const fetchJoinAddressByDayServer = async (timestamp, addresses, identifier) => {
|
||||
let url = `/v1/graph?type=joinAddressByDay&server=${identifier}&addresses=${addresses.join(',')}`;
|
||||
if (staticSite) url = `/data/graph-joinAddressByDay_${identifier}.json`;
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
const fetchJoinAddressByDayNetwork = async (timestamp) => {
|
||||
let url = `/v1/graph?type=joinAddressByDay`;
|
||||
const fetchJoinAddressByDayNetwork = async (timestamp, addresses) => {
|
||||
let url = `/v1/graph?type=joinAddressByDay&addresses=${addresses.join(',')}`;
|
||||
if (staticSite) url = `/data/graph-joinAddressByDay.json`;
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
@ -321,22 +301,22 @@ const fetchNetworkRetentionData = async (timestamp) => {
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchPlayerJoinAddresses = async (timestamp, identifier) => {
|
||||
export const fetchPlayerJoinAddresses = async (timestamp, identifier, justList) => {
|
||||
if (identifier) {
|
||||
return await fetchServerPlayerJoinAddresses(timestamp, identifier);
|
||||
return await fetchServerPlayerJoinAddresses(timestamp, identifier, justList);
|
||||
} else {
|
||||
return await fetchNetworkPlayerJoinAddresses(timestamp);
|
||||
return await fetchNetworkPlayerJoinAddresses(timestamp, justList);
|
||||
}
|
||||
}
|
||||
|
||||
const fetchServerPlayerJoinAddresses = async (timestamp, identifier) => {
|
||||
let url = `/v1/joinAddresses?server=${identifier}`;
|
||||
const fetchServerPlayerJoinAddresses = async (timestamp, identifier, justList) => {
|
||||
let url = `/v1/joinAddresses?server=${identifier}${justList ? "&listOnly=true" : ""}`;
|
||||
if (staticSite) url = `/data/joinAddresses-${identifier}.json`;
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
const fetchNetworkPlayerJoinAddresses = async (timestamp) => {
|
||||
let url = `/v1/joinAddresses`;
|
||||
const fetchNetworkPlayerJoinAddresses = async (timestamp, justList) => {
|
||||
let url = `/v1/joinAddresses${justList ? "?listOnly=true" : ""}`;
|
||||
if (staticSite) url = `/data/joinAddresses.json`;
|
||||
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 {Col} from "react-bootstrap";
|
||||
import JoinAddressGroupCard from "../../components/cards/server/graphs/JoinAddressGroupCard";
|
||||
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";
|
||||
import JoinAddresses from "../../components/cards/common/JoinAddresses.jsx";
|
||||
import {useAuth} from "../../hooks/authenticationHook.jsx";
|
||||
|
||||
const NetworkJoinAddresses = () => {
|
||||
const {hasPermission} = useAuth();
|
||||
|
||||
const seeTime = hasPermission('page.network.join.addresses.graphs.time');
|
||||
const seeLatest = hasPermission('page.network.join.addresses.graphs.pie');
|
||||
return (
|
||||
<LoadIn>
|
||||
<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>
|
||||
<JoinAddresses id={'network-join-addresses'} identifier={null} seeTime={seeTime}/>
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -1,24 +1,12 @@
|
||||
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 PlayerRetention from "../../components/cards/common/PlayerRetention.jsx";
|
||||
|
||||
const NetworkPlayerRetention = () => {
|
||||
const {hasPermission} = useAuth();
|
||||
|
||||
const seeRetention = hasPermission('page.network.retention');
|
||||
return (
|
||||
<LoadIn>
|
||||
{seeRetention && <section className="network-retention">
|
||||
<ExtendableRow id={'row-network-retention-0'}>
|
||||
<Col lg={12}>
|
||||
<PlayerRetentionGraphCard identifier={null}/>
|
||||
</Col>
|
||||
</ExtendableRow>
|
||||
</section>}
|
||||
</LoadIn>
|
||||
<PlayerRetention id={"network-retention"} identifier={null} seeRetention={seeRetention}/>
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -1,31 +1,14 @@
|
||||
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 LoadIn from "../../components/animation/LoadIn";
|
||||
import ExtendableRow from "../../components/layout/extension/ExtendableRow";
|
||||
import {useAuth} from "../../hooks/authenticationHook";
|
||||
import JoinAddresses from "../../components/cards/common/JoinAddresses.jsx";
|
||||
import {useAuth} from "../../hooks/authenticationHook.jsx";
|
||||
|
||||
const ServerJoinAddresses = () => {
|
||||
const {hasPermission} = useAuth();
|
||||
const {identifier} = useParams();
|
||||
|
||||
const {hasPermission} = useAuth();
|
||||
const seeTime = hasPermission('page.server.join.addresses.graphs.time');
|
||||
const seeLatest = hasPermission('page.server.join.addresses.graphs.pie');
|
||||
return (
|
||||
<LoadIn>
|
||||
<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>
|
||||
<JoinAddresses id={'server-join-addresses'} identifier={identifier} seeTime={seeTime}/>
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,7 @@
|
||||
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 {useAuth} from "../../hooks/authenticationHook";
|
||||
import PlayerRetention from "../../components/cards/common/PlayerRetention.jsx";
|
||||
|
||||
const ServerPlayerRetention = () => {
|
||||
const {hasPermission} = useAuth();
|
||||
@ -12,15 +9,7 @@ const ServerPlayerRetention = () => {
|
||||
|
||||
const seeRetention = hasPermission('page.server.retention');
|
||||
return (
|
||||
<LoadIn>
|
||||
<section className="server-retention">
|
||||
{seeRetention && <ExtendableRow id={'row-server-retention-0'}>
|
||||
<Col lg={12}>
|
||||
<PlayerRetentionGraphCard identifier={identifier}/>
|
||||
</Col>
|
||||
</ExtendableRow>}
|
||||
</section>
|
||||
</LoadIn>
|
||||
<PlayerRetention id={"server-retention"} identifier={identifier} seeRetention={seeRetention}/>
|
||||
)
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user