diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java index fe835068b..373957556 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/listeners/bukkit/PlayerOnlineListener.java @@ -28,6 +28,7 @@ import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction; import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction; +import com.djrapitops.plan.storage.database.transactions.events.StoreAllowlistBounceTransaction; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; import org.bukkit.event.EventHandler; @@ -82,6 +83,11 @@ public class PlayerOnlineListener implements Listener { UUID playerUUID = event.getPlayer().getUniqueId(); ServerUUID serverUUID = serverInfo.getServerUUID(); boolean banned = PlayerLoginEvent.Result.KICK_BANNED == event.getResult(); + boolean notWhitelisted = PlayerLoginEvent.Result.KICK_WHITELIST == event.getResult(); + + if (notWhitelisted) { + dbSystem.getDatabase().executeTransaction(new StoreAllowlistBounceTransaction(playerUUID, event.getPlayer().getName(), serverUUID, System.currentTimeMillis())); + } String address = event.getHostname(); if (!address.isEmpty()) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/WebPermission.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/WebPermission.java index 175e578de..bfb955960 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/WebPermission.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/WebPermission.java @@ -93,6 +93,7 @@ public enum WebPermission implements Supplier, Lang { PAGE_SERVER_PERFORMANCE_OVERVIEW("See Performance numbers"), PAGE_SERVER_PLUGIN_HISTORY("See Plugin History"), PAGE_SERVER_PLUGINS("See Plugins -tabs of servers"), + PAGE_SERVER_ALLOWLIST_BOUNCE("See list of Game allowlist bounces"), PAGE_PLAYER("See all of player page"), PAGE_PLAYER_OVERVIEW("See Player Overview -tab"), diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/AllowlistBounce.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/AllowlistBounce.java new file mode 100644 index 000000000..3d80228fe --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/AllowlistBounce.java @@ -0,0 +1,82 @@ +/* + * 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 . + */ +package com.djrapitops.plan.delivery.domain.datatransfer; + +import com.djrapitops.plan.utilities.dev.Untrusted; + +import java.util.Objects; +import java.util.UUID; + +/** + * Represents an event where player bounced off the whitelist. + * + * @author AuroraLS3 + */ +public class AllowlistBounce { + + private final UUID playerUUID; + @Untrusted + private final String playerName; + private final int count; + private final long lastTime; + + public AllowlistBounce(UUID playerUUID, String playerName, int count, long lastTime) { + this.playerUUID = playerUUID; + this.playerName = playerName; + this.count = count; + this.lastTime = lastTime; + } + + public UUID getPlayerUUID() { + return playerUUID; + } + + public String getPlayerName() { + return playerName; + } + + public int getCount() { + return count; + } + + public long getLastTime() { + return lastTime; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AllowlistBounce bounce = (AllowlistBounce) o; + return getCount() == bounce.getCount() && getLastTime() == bounce.getLastTime() && Objects.equals(getPlayerUUID(), bounce.getPlayerUUID()) && Objects.equals(getPlayerName(), bounce.getPlayerName()); + } + + @Override + public int hashCode() { + return Objects.hash(getPlayerUUID(), getPlayerName(), getCount(), getLastTime()); + } + + @Override + public String toString() { + return "AllowlistBounce{" + + "playerUUID=" + playerUUID + + ", playerName='" + playerName + '\'' + + ", count=" + count + + ", lastTime=" + lastTime + + '}'; + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/DataID.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/DataID.java index 755f1a27e..2a852d44a 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/DataID.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/cache/DataID.java @@ -57,6 +57,7 @@ public enum DataID { JOIN_ADDRESSES_BY_DAY, PLAYER_RETENTION, PLAYER_JOIN_ADDRESSES, + PLAYER_ALLOWLIST_BOUNCES, ; public String of(ServerUUID serverUUID) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/AllowlistJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/AllowlistJSONResolver.java new file mode 100644 index 000000000..053302d27 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/AllowlistJSONResolver.java @@ -0,0 +1,119 @@ +/* + * 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 . + */ +package com.djrapitops.plan.delivery.webserver.resolver.json; + +import com.djrapitops.plan.delivery.domain.auth.WebPermission; +import com.djrapitops.plan.delivery.formatting.Formatter; +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.WebUser; +import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService; +import com.djrapitops.plan.delivery.webserver.cache.DataID; +import com.djrapitops.plan.delivery.webserver.cache.JSONStorage; +import com.djrapitops.plan.identification.Identifiers; +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.Database; +import com.djrapitops.plan.storage.database.queries.objects.AllowlistQueries; +import com.djrapitops.plan.storage.database.queries.objects.SessionQueries; +import com.djrapitops.plan.utilities.dev.Untrusted; +import io.swagger.v3.oas.annotations.Operation; +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.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import org.jetbrains.annotations.Nullable; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Map; +import java.util.Optional; + +/** + * Response resolver to get game allowlist bounces. + * + * @author AuroraLS3 + */ +@Singleton +@Path("/v1/gameAllowlistBounces") +public class AllowlistJSONResolver extends JSONResolver { + + private final DBSystem dbSystem; + private final Identifiers identifiers; + private final AsyncJSONResolverService jsonResolverService; + + @Inject + public AllowlistJSONResolver(DBSystem dbSystem, Identifiers identifiers, AsyncJSONResolverService jsonResolverService) { + this.dbSystem = dbSystem; + this.identifiers = identifiers; + this.jsonResolverService = jsonResolverService; + } + + @Override + public Formatter getHttpLastModifiedFormatter() {return jsonResolverService.getHttpLastModifiedFormatter();} + + @Override + public boolean canAccess(@Untrusted Request request) { + WebUser user = request.getUser().orElse(new WebUser("")); + + return user.hasPermission(WebPermission.PAGE_SERVER_ALLOWLIST_BOUNCE); + } + + @GET + @Operation( + description = "Get allowlist bounce data for server", + responses = { + @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)), + @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 = { + @ExampleObject("Server 1"), + @ExampleObject("1"), + @ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"), + }), + requestBody = @RequestBody(content = @Content(examples = @ExampleObject())) + ) + @Override + public Optional resolve(@Untrusted Request request) { + return Optional.of(getResponse(request)); + } + + private Response getResponse(@Untrusted Request request) { + JSONStorage.StoredJSON result = getStoredJSON(request); + return getCachedOrNewResponse(request, result); + } + + @Nullable + private JSONStorage.StoredJSON getStoredJSON(Request request) { + Optional timestamp = Identifiers.getTimestamp(request); + + ServerUUID serverUUID = identifiers.getServerUUID(request); + Database database = dbSystem.getDatabase(); + return jsonResolverService.resolve(timestamp, DataID.PLAYER_ALLOWLIST_BOUNCES, serverUUID, + theUUID -> Map.of( + "allowlist_bounces", database.query(AllowlistQueries.getBounces(serverUUID)), + "last_seen_by_uuid", database.query(SessionQueries.lastSeen(serverUUID)) + ) + ); + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/RootJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/RootJSONResolver.java index 8e892eed8..497ad3137 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/RootJSONResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/RootJSONResolver.java @@ -90,6 +90,7 @@ public class RootJSONResolver { RetentionJSONResolver retentionJSONResolver, PlayerJoinAddressJSONResolver playerJoinAddressJSONResolver, PluginHistoryJSONResolver pluginHistoryJSONResolver, + AllowlistJSONResolver allowlistJSONResolver, PreferencesJSONResolver preferencesJSONResolver, StorePreferencesJSONResolver storePreferencesJSONResolver, @@ -129,7 +130,8 @@ public class RootJSONResolver { .add("extensionData", extensionJSONResolver) .add("retention", retentionJSONResolver) .add("joinAddresses", playerJoinAddressJSONResolver) - .add("preferences", preferencesJSONResolver); + .add("preferences", preferencesJSONResolver) + .add("gameAllowlistBounces", allowlistJSONResolver); this.webServer = webServer; // These endpoints require authentication to be enabled. diff --git a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java index 24c1c78aa..b65db83dd 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java @@ -285,6 +285,15 @@ public enum HtmlLang implements Lang { LABEL_TABLE_SHOW_PER_PAGE("html.label.table.showPerPage", "Show per page"), LABEL_EXPORT("html.label.export", "Export"), + LABEL_ALLOWLIST("html.label.allowlist", "Allowlist"), + LABEL_ALLOWLIST_BOUNCES("html.label.allowlistBounces", "Allowlist Bounces"), + LABEL_ATTEMPTS("html.label.attempts", "Attempts"), + LABEL_LAST_KNOWN_ATTEMPT("html.label.lastKnownAttempt", "Last Known Attempt"), + LABEL_PREVIOUS_ATTEMPT("html.label.lastBlocked", "Last Blocked"), + LABEL_LAST_ALLOWED_LOGIN("html.label.lastAllowed", "Last Allowed"), + LABEL_BLOCKED("html.label.blocked", "Blocked"), + LABEL_ALLOWED("html.label.allowed", "Allowed"), + LOGIN_LOGIN("html.login.login", "Login"), LOGIN_LOGOUT("html.login.logout", "Logout"), LOGIN_USERNAME("html.login.username", "Username"), diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java index 6a89a47ba..1ccb68e4d 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/SQLDB.java @@ -147,6 +147,10 @@ public abstract class SQLDB extends AbstractDatabase { } } + public static ThreadLocal getTransactionOrigin() { + return TRANSACTION_ORIGIN; + } + @Override public void init() { List unfinishedTransactions = forceCloseTransactionExecutor(); @@ -187,22 +191,6 @@ public abstract class SQLDB extends AbstractDatabase { return true; } - protected List forceCloseTransactionExecutor() { - if (transactionExecutor == null || transactionExecutor.isShutdown() || transactionExecutor.isTerminated()) { - return Collections.emptyList(); - } - try { - List unfinished = transactionExecutor.shutdownNow(); - int unfinishedCount = unfinished.size(); - if (unfinishedCount > 0) { - logger.warn(unfinishedCount + " unfinished database transactions were not executed."); - } - return unfinished; - } finally { - logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE)); - } - } - Patch[] patches() { return new Patch[]{ new Version10Patch(), @@ -313,6 +301,22 @@ public abstract class SQLDB extends AbstractDatabase { */ public abstract void setupDataSource(); + protected List forceCloseTransactionExecutor() { + if (transactionExecutor == null || transactionExecutor.isShutdown() || transactionExecutor.isTerminated()) { + return Collections.emptyList(); + } + try { + List unfinished = transactionExecutor.shutdownNow(); + int unfinishedCount = unfinished.size(); + if (unfinishedCount > 0) { + logger.warn(unfinishedCount + " unfinished database transactions were not executed."); + } + return unfinished; + } finally { + logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE)); + } + } + @Override public void close() { // SQLiteDB Overrides this, so any additions to this should also be reflected there. @@ -326,13 +330,6 @@ public abstract class SQLDB extends AbstractDatabase { setState(State.CLOSED); } - protected void unloadDriverClassloader() { - // Unloading class loader using close() causes issues when reloading. - // It is better to leak this memory than crash the plugin on reload. - - driverClassLoader = null; - } - public abstract Connection getConnection() throws SQLException; public abstract void returnToPool(Connection connection); @@ -346,8 +343,11 @@ public abstract class SQLDB extends AbstractDatabase { return accessLock.performDatabaseOperation(() -> query.executeQuery(this), transaction); } - public static ThreadLocal getTransactionOrigin() { - return TRANSACTION_ORIGIN; + protected void unloadDriverClassloader() { + // Unloading class loader using close() causes issues when reloading. + // It is better to leak this memory than crash the plugin on reload. + + driverClassLoader = null; } @Override diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/AllowlistQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/AllowlistQueries.java new file mode 100644 index 000000000..1cfb989d1 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/AllowlistQueries.java @@ -0,0 +1,63 @@ +/* + * 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 . + */ +package com.djrapitops.plan.storage.database.queries.objects; + +import com.djrapitops.plan.delivery.domain.datatransfer.AllowlistBounce; +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.queries.Query; +import com.djrapitops.plan.storage.database.sql.tables.AllowlistBounceTable; +import com.djrapitops.plan.storage.database.sql.tables.ServerTable; +import org.intellij.lang.annotations.Language; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import static com.djrapitops.plan.storage.database.sql.building.Sql.*; + +/** + * Query against {@link AllowlistBounceTable}. + * + * @author AuroraLS3 + */ +public class AllowlistQueries { + + private AllowlistQueries() { + /* Static method class */ + } + + public static Query> getBounces(ServerUUID serverUUID) { + @Language("SQL") String sql = SELECT + + AllowlistBounceTable.UUID + ',' + + AllowlistBounceTable.USER_NAME + ',' + + AllowlistBounceTable.TIMES + ',' + + AllowlistBounceTable.LAST_BOUNCE + + FROM + AllowlistBounceTable.TABLE_NAME + + WHERE + AllowlistBounceTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; + return db -> db.queryList(sql, AllowlistQueries::extract, serverUUID); + } + + private static AllowlistBounce extract(ResultSet set) throws SQLException { + return new AllowlistBounce( + UUID.fromString(set.getString(AllowlistBounceTable.UUID)), + set.getString(AllowlistBounceTable.USER_NAME), + set.getInt(AllowlistBounceTable.TIMES), + set.getLong(AllowlistBounceTable.LAST_BOUNCE) + ); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java index f3be93eb2..96dc55925 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java @@ -1014,4 +1014,16 @@ public class SessionQueries { } }; } + + public static Query> lastSeen(ServerUUID serverUUID) { + String sql = SELECT + UsersTable.USER_UUID + ", MAX(" + SessionsTable.SESSION_END + ") as last_seen" + + FROM + SessionsTable.TABLE_NAME + " s" + + INNER_JOIN + UsersTable.TABLE_NAME + " u ON u." + UsersTable.ID + "=s." + SessionsTable.USER_ID + + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + + GROUP_BY + UsersTable.USER_UUID; + return db -> db.queryMap(sql, (set, to) -> to.put( + UUID.fromString(set.getString(UsersTable.USER_UUID)), + set.getLong("last_seen") + ), serverUUID); + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/AllowlistBounceTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/AllowlistBounceTable.java new file mode 100644 index 000000000..a3e240693 --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/AllowlistBounceTable.java @@ -0,0 +1,70 @@ +/* + * 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 . + */ +package com.djrapitops.plan.storage.database.sql.tables; + +import com.djrapitops.plan.storage.database.DBType; +import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder; +import com.djrapitops.plan.storage.database.sql.building.Sql; +import org.intellij.lang.annotations.Language; + +/** + * Represents plan_allowlist_bounce table. + * + * @author AuroraLS3 + */ +public class AllowlistBounceTable { + + public static final String TABLE_NAME = "plan_allowlist_bounce"; + + public static final String ID = "id"; + public static final String UUID = "uuid"; + public static final String USER_NAME = "name"; + public static final String SERVER_ID = "server_id"; + public static final String TIMES = "times"; + public static final String LAST_BOUNCE = "last_bounce"; + + @Language("SQL") + public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" + + UUID + ',' + + USER_NAME + ',' + + SERVER_ID + ',' + + TIMES + ',' + + LAST_BOUNCE + + ") VALUES (?,?," + ServerTable.SELECT_SERVER_ID + ",?,?)"; + + @Language("SQL") + public static final String INCREMENT_TIMES_STATEMENT = "UPDATE " + TABLE_NAME + + " SET " + TIMES + "=" + TIMES + "+1, " + LAST_BOUNCE + "=?" + + " WHERE " + UUID + "=?" + + " AND " + SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID; + + private AllowlistBounceTable() { + /* Static information class */ + } + + public static String createTableSQL(DBType dbType) { + return CreateTableBuilder.create(TABLE_NAME, dbType) + .column(ID, Sql.INT).primaryKey() + .column(UUID, Sql.varchar(36)).notNull().unique() + .column(USER_NAME, Sql.varchar(36)).notNull() + .column(SERVER_ID, Sql.INT).notNull() + .column(TIMES, Sql.INT).notNull().defaultValue("0") + .column(LAST_BOUNCE, Sql.LONG).notNull() + .foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.ID) + .toString(); + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ServerTable.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ServerTable.java index c2658d7d1..37e1ca0a2 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ServerTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ServerTable.java @@ -24,6 +24,7 @@ import com.djrapitops.plan.storage.database.sql.building.Insert; import com.djrapitops.plan.storage.database.sql.building.Sql; import com.djrapitops.plan.storage.database.sql.building.Update; import org.apache.commons.text.TextStringBuilder; +import org.intellij.lang.annotations.Language; import java.util.Collection; @@ -61,6 +62,7 @@ public class ServerTable { .where(SERVER_UUID + "=?") .toString(); + @Language("SQL") public static final String SELECT_SERVER_ID = '(' + SELECT + TABLE_NAME + '.' + ID + FROM + TABLE_NAME + diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemoveEverythingTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemoveEverythingTransaction.java index 27cf6f549..b41c5c3c8 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemoveEverythingTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/commands/RemoveEverythingTransaction.java @@ -43,6 +43,7 @@ public class RemoveEverythingTransaction extends Patch { clearTable(WorldTimesTable.TABLE_NAME); clearTable(SessionsTable.TABLE_NAME); clearTable(JoinAddressTable.TABLE_NAME); + clearTable(AllowlistBounceTable.TABLE_NAME); clearTable(WorldTable.TABLE_NAME); clearTable(PingTable.TABLE_NAME); clearTable(UserInfoTable.TABLE_NAME); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreAllowlistBounceTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreAllowlistBounceTransaction.java new file mode 100644 index 000000000..d61e3f81f --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/events/StoreAllowlistBounceTransaction.java @@ -0,0 +1,72 @@ +/* + * 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 . + */ +package com.djrapitops.plan.storage.database.transactions.events; + +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.sql.tables.AllowlistBounceTable; +import com.djrapitops.plan.storage.database.transactions.ExecStatement; +import com.djrapitops.plan.storage.database.transactions.Transaction; +import com.djrapitops.plan.utilities.dev.Untrusted; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.UUID; + +/** + * Stores a bounced allowlist login. + * + * @author AuroraLS3 + */ +public class StoreAllowlistBounceTransaction extends Transaction { + + private final UUID playerUUID; + @Untrusted + private final String playerName; + private final ServerUUID serverUUID; + private final long time; + + public StoreAllowlistBounceTransaction(UUID playerUUID, @Untrusted String playerName, ServerUUID serverUUID, long time) { + this.playerUUID = playerUUID; + this.playerName = playerName; + this.serverUUID = serverUUID; + this.time = time; + } + + @Override + protected void performOperations() { + boolean updated = execute(new ExecStatement(AllowlistBounceTable.INCREMENT_TIMES_STATEMENT) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setLong(1, time); + statement.setString(2, playerUUID.toString()); + statement.setString(3, serverUUID.toString()); + } + }); + if (!updated) { + execute(new ExecStatement(AllowlistBounceTable.INSERT_STATEMENT) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, playerUUID.toString()); + statement.setString(2, playerName); + statement.setString(3, serverUUID.toString()); + statement.setInt(4, 1); + statement.setLong(5, time); + } + }); + } + } +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateTablesTransaction.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateTablesTransaction.java index 6615cfe9a..28d6e91a3 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateTablesTransaction.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateTablesTransaction.java @@ -59,6 +59,7 @@ public class CreateTablesTransaction extends OperationCriticalTransaction { executeOther(new SecurityTableIdPatch()); execute(WebUserPreferencesTable.createTableSQL(dbType)); execute(PluginVersionTable.createTableSQL(dbType)); + execute(AllowlistBounceTable.createTableSQL(dbType)); // DataExtension tables execute(ExtensionIconTable.createTableSQL(dbType)); diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_CN.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_CN.yml index ec313f89d..6bb3fe49c 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_CN.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_CN.yml @@ -294,9 +294,13 @@ html: afkTime: "挂机时间" all: "全部" allTime: "所有时间" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "按字母顺序" apply: "应用" asNumbers: "数据" + attempts: "Attempts" average: "平均" averageActivePlaytime: "平均活跃时间" averageAfkTime: "平均挂机时间" @@ -317,6 +321,7 @@ html: banned: "已被封禁" bestPeak: "历史最高峰值" bestPing: "最低延迟" + blocked: "Blocked" calendar: " 日历" comparing7days: "对比 7 天的情况" connectionInfo: "连接信息" @@ -430,7 +435,10 @@ html: last24hours: "过去 24 小时" last30days: "过去 30 天" last7days: "过去 7 天" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "最后连接时间" + lastKnownAttempt: "Last Known Attempt" lastPeak: "上次在线峰值" lastSeen: "最后在线时间" latestJoinAddresses: "上一次加入地址" @@ -686,6 +694,7 @@ html: page_player_sessions: "查看玩家会话 - 选项卡" page_player_versus: "查看PvP和PvE - 选项卡" page_server: "查看所有服务器页面" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "查看服务器地理位置 - 选项卡" page_server_geolocations_map: "查看服务器地理位置地图" page_server_geolocations_ping_per_country: "查看按国家划分的延迟表" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_CS.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_CS.yml index 046a02ffc..c9365f39f 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_CS.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_CS.yml @@ -294,9 +294,13 @@ html: afkTime: "AFK čas" all: "Vše" allTime: "Celkově" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Abecední řazení" apply: "Apply" asNumbers: "statistiky" + attempts: "Attempts" average: "Průměrná délka prvního připojení" averageActivePlaytime: "Průměrná herní aktivita" averageAfkTime: "Průměrný AFK čas" @@ -317,6 +321,7 @@ html: banned: "Zabanován" bestPeak: "Nejvíce hráčů" bestPing: "Nejlepší ping" + blocked: "Blocked" calendar: " Kalendář" comparing7days: "Srovnání posledních 7 dní" connectionInfo: "Informace o připojení" @@ -430,7 +435,10 @@ html: last24hours: "Posledních 24 hodin" last30days: "Posledních 30 dní" last7days: "Posledních 7 dní" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Poslední připojení" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Naposledy nejvíce hráčů" lastSeen: "Naposledy viděn" latestJoinAddresses: "Poslední adresy pro připojení" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_DE.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_DE.yml index 3435a267e..35f6e73a9 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_DE.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_DE.yml @@ -294,9 +294,13 @@ html: afkTime: "AFK Zeit" all: "Gesamt" allTime: "Gesamte zeit" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Alphabetical" apply: "Apply" asNumbers: "als Zahlen" + attempts: "Attempts" average: "Average first session length" averageActivePlaytime: "Durchschnittliche aktive Spielzeit" averageAfkTime: "Durchschnittliche AFK Zeit" @@ -317,6 +321,7 @@ html: banned: "Gebannt" bestPeak: "Rekord" bestPing: "Bester Ping" + blocked: "Blocked" calendar: " Kalender" comparing7days: "Vergleiche 7 Tage" connectionInfo: "Verbindungsinformationen" @@ -430,7 +435,10 @@ html: last24hours: "Letzte 24 Stunden" last30days: "Letzte 30 Tage" last7days: "Letzte 7 Tage" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Letzte Verbindung" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Letzter Höchststand" lastSeen: "Zuletzt gesehen" latestJoinAddresses: "Latest Join Addresses" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml index f0f125e29..ab289f0ca 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_EN.yml @@ -294,9 +294,13 @@ html: afkTime: "AFK Time" all: "All" allTime: "All Time" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Alphabetical" apply: "Apply" asNumbers: "as Numbers" + attempts: "Attempts" average: "Average first session length" averageActivePlaytime: "Average Active Playtime" averageAfkTime: "Average AFK Time" @@ -317,6 +321,7 @@ html: banned: "Banned" bestPeak: "All Time Peak" bestPing: "Best Ping" + blocked: "Blocked" calendar: " Calendar" comparing7days: "Comparing 7 days" connectionInfo: "Connection Information" @@ -430,7 +435,10 @@ html: last24hours: "Last 24 hours" last30days: "Last 30 days" last7days: "Last 7 days" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Last Connected" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Last Peak" lastSeen: "Last Seen" latestJoinAddresses: "Latest Join Addresses" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_ES.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_ES.yml index 3a3a09da7..72fa2d836 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_ES.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_ES.yml @@ -294,9 +294,13 @@ html: afkTime: "Tiempo AFK" all: "Todo" allTime: "Todo el tiempo" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Alphabetical" apply: "Apply" asNumbers: "como números" + attempts: "Attempts" average: "Average first session length" averageActivePlaytime: "Tiempo de juego activo promedio" averageAfkTime: "Tiempo AFK promedio" @@ -317,6 +321,7 @@ html: banned: "Baneado" bestPeak: "Mejor pico" bestPing: "Mejor Ping" + blocked: "Blocked" calendar: " Calendario" comparing7days: "Comparando 7 dias" connectionInfo: "Información de conexión" @@ -430,7 +435,10 @@ html: last24hours: "Últimas 24 horas" last30days: "Últimos 30 dias" last7days: "Últimos 7 días" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Última vez conectado" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Último pico" lastSeen: "Última vez visto" latestJoinAddresses: "Latest Join Addresses" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_FI.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_FI.yml index 860a6b285..700041a94 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_FI.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_FI.yml @@ -294,9 +294,13 @@ html: afkTime: "Aika AFK:ina" all: "Kaikki" allTime: "Kaikkien aikojen" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Aakkosjärjestys" apply: "Käytä" asNumbers: "Numeroina" + attempts: "Attempts" average: "Keskimäräinen" averageActivePlaytime: "Keskimäräinen Aktiivinen peliaika" averageAfkTime: "Keskimäräinen AFK aika" @@ -317,6 +321,7 @@ html: banned: "Pannassa" bestPeak: "Paras Huippu" bestPing: "Paras Vasteaika" + blocked: "Blocked" calendar: " Kalenteri" comparing7days: "Verrataan 7 päivää" connectionInfo: "Yhteyksien tiedot" @@ -430,7 +435,10 @@ html: last24hours: "Viimeiset 24 tuntia" last30days: "Viimeiset 30 päivää" last7days: "Viimeiset 7 päivää" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Viimeisin yhteys" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Viimeisin huippu" lastSeen: "Nähty Viimeksi" latestJoinAddresses: "Viimeisimmät Liittymisosoitteet" @@ -686,6 +694,7 @@ html: page_player_sessions: "Näkee Pelaajan Istunnot osion" page_player_versus: "Näkee PvP & PvE osion" page_server: "Näkee koko palvelin sivun" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "Näkee Geolokaatio osion" page_server_geolocations_map: "Näkee Geolokaatio kartan" page_server_geolocations_ping_per_country: "Näkee Viive per Maa -taulun" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_FR.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_FR.yml index fd5afb430..6d06d8256 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_FR.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_FR.yml @@ -294,9 +294,13 @@ html: afkTime: "Temps AFK" all: "Tout" allTime: "Tout le Temps" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Alphabetical" apply: "Apply" asNumbers: "en Chiffres" + attempts: "Attempts" average: "Average first session length" averageActivePlaytime: "Temps Actif moyen" averageAfkTime: "Temps AFK moyen" @@ -317,6 +321,7 @@ html: banned: "Banni(e)" bestPeak: "Pic maximal de Joueurs en Ligne" bestPing: "Meilleure Latence" + blocked: "Blocked" calendar: " Calendrier" comparing7days: "Comparaison des 7 derniers Jours" connectionInfo: "Renseignements sur la Connexion" @@ -430,7 +435,10 @@ html: last24hours: "24 Dernières heures" last30days: "30 Derniers jours" last7days: "7 Derniers jours" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Dernier Connecté" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Dernier pic de Joueurs en Ligne" lastSeen: "Dernière Connexion" latestJoinAddresses: "Latest Join Addresses" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_IT.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_IT.yml index 17e1f798d..7bfa3aac5 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_IT.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_IT.yml @@ -294,9 +294,13 @@ html: afkTime: "Tempo AFK" all: "Tutto" allTime: "Tutto il Tempo" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Alphabetical" apply: "Apply" asNumbers: "Statistiche" + attempts: "Attempts" average: "Average first session length" averageActivePlaytime: "Average Active Playtime" averageAfkTime: "Average AFK Time" @@ -317,6 +321,7 @@ html: banned: "Bannato" bestPeak: "Record Migliore" bestPing: "Ping Migliore" + blocked: "Blocked" calendar: " Calendario" comparing7days: "Comparazione di 7 giorni" connectionInfo: "Informazioni sulla Connessione" @@ -430,7 +435,10 @@ html: last24hours: "Ultime 24 ore" last30days: "Ultimi 30 giorni" last7days: "Ultimi 7 giorni" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Ultima connessione" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Record Settimanale" lastSeen: "Ultima Visita" latestJoinAddresses: "Latest Join Addresses" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_JA.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_JA.yml index 93687258c..c85068f2e 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_JA.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_JA.yml @@ -294,9 +294,13 @@ html: afkTime: "離席時間" all: "全て" allTime: "全体" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "アルファベット順" apply: "適用" asNumbers: "の情報" + attempts: "Attempts" average: "平均の初回セッション時間" averageActivePlaytime: "平均アクティブプレイ時間" averageAfkTime: "平均AFK時間" @@ -317,6 +321,7 @@ html: banned: "BAN履歴" bestPeak: "全体のピークタイム" bestPing: "最高Ping値" + blocked: "Blocked" calendar: "カレンダー" comparing7days: "直近1週間との比較" connectionInfo: "接続情報" @@ -430,7 +435,10 @@ html: last24hours: "24時間" last30days: "1ヶ月" last7days: "1週間" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "直近の接続" + lastKnownAttempt: "Last Known Attempt" lastPeak: "直近のピークタイム" lastSeen: "直近のオンライン" latestJoinAddresses: "最後に参加したサーバーのアドレス" @@ -686,6 +694,7 @@ html: page_player_sessions: "プレイヤーセッションタブを表示" page_player_versus: "PvP & PvEタブを表示" page_server: "全てのサーバーページを表示" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "ジオロケーションタブを表示" page_server_geolocations_map: "ジオロケーションマップを表示" page_server_geolocations_ping_per_country: "国ごとのPing表を表示" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_KO.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_KO.yml index 0d1bdba83..97148034d 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_KO.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_KO.yml @@ -294,9 +294,13 @@ html: afkTime: "AFK 시간" all: "모두" allTime: "모든 시간" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Alphabetical" apply: "Apply" asNumbers: "숫자로" + attempts: "Attempts" average: "Average first session length" averageActivePlaytime: "Average Active Playtime" averageAfkTime: "Average AFK Time" @@ -317,6 +321,7 @@ html: banned: "Banned" bestPeak: "최고의 피크" bestPing: "최고 Ping" + blocked: "Blocked" calendar: " 달력" comparing7days: "지난 7일 비교" connectionInfo: "연결 정보" @@ -430,7 +435,10 @@ html: last24hours: "지난 24시간" last30days: "지난 30일" last7days: "지난 7일" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "마지막 연결" + lastKnownAttempt: "Last Known Attempt" lastPeak: "마지막 피크" lastSeen: "마지막으로 본" latestJoinAddresses: "Latest Join Addresses" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_NL.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_NL.yml index b64e1c772..67d6a0303 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_NL.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_NL.yml @@ -294,9 +294,13 @@ html: afkTime: "AFK Tijd" all: "Alle" allTime: "Alle Tijd" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Alphabetical" apply: "Apply" asNumbers: "als nummers" + attempts: "Attempts" average: "Average first session length" averageActivePlaytime: "Gemiddelde Actieve Speeltijd" averageAfkTime: "Gemiddelde AFK Tijd" @@ -317,6 +321,7 @@ html: banned: "Verbannen" bestPeak: "Piek aller tijden" bestPing: "Beste ping" + blocked: "Blocked" calendar: " Kalender" comparing7days: "7 dagen vergelijken" connectionInfo: "Verbindingsinformatie" @@ -430,7 +435,10 @@ html: last24hours: "Afgelopen 24 uur" last30days: "Afgelopen 30 dagen" last7days: "Afgelopen 7 dagen" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Laatst verbonden" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Laatste piek" lastSeen: "Laatste gezien" latestJoinAddresses: "Latest Join Addresses" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_PT_BR.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_PT_BR.yml index 52e5c985a..055993a19 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_PT_BR.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_PT_BR.yml @@ -294,9 +294,13 @@ html: afkTime: "AFK Time" all: "Todos" allTime: "All Time" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Alphabetical" apply: "Apply" asNumbers: "as Numbers" + attempts: "Attempts" average: "Average first session length" averageActivePlaytime: "Average Active Playtime" averageAfkTime: "Average AFK Time" @@ -317,6 +321,7 @@ html: banned: "Banido" bestPeak: "Pico Máximo" bestPing: "Best Ping" + blocked: "Blocked" calendar: " Calendário" comparing7days: "Comparing 7 days" connectionInfo: "Connection Information" @@ -430,7 +435,10 @@ html: last24hours: "Últimas 24 horas" last30days: "Últimos 30 dias" last7days: "Últimos 7 dias" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Última Conexão" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Último Pico" lastSeen: "Última Vez Visto" latestJoinAddresses: "Latest Join Addresses" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_RU.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_RU.yml index 728500c5c..0c009a846 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_RU.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_RU.yml @@ -294,9 +294,13 @@ html: afkTime: "Время AFK" all: "Все" allTime: "Все время" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Alphabetical" apply: "Apply" asNumbers: "В числах" + attempts: "Attempts" average: "Средняя продолжительность первого сеанса" averageActivePlaytime: "Среднее время активной игры" averageAfkTime: "Среднее время AFK" @@ -317,6 +321,7 @@ html: banned: "Забанен" bestPeak: "Максимальный Пик" bestPing: "Наилучший пинг" + blocked: "Blocked" calendar: " Календарь" comparing7days: "Сравнение 7 дней" connectionInfo: "Информация о соединении" @@ -430,7 +435,10 @@ html: last24hours: "Последние 24 часа" last30days: "Последние 30 дней" last7days: "Последние 7 дней" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Последнее подключение" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Последний Пик" lastSeen: "Последнее посещение" latestJoinAddresses: "Latest Join Addresses" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_TR.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_TR.yml index d6a5a53a8..8eb37e2e7 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_TR.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_TR.yml @@ -294,9 +294,13 @@ html: afkTime: "AFK Süresi" all: "Tamamı" allTime: "Tüm zamanlar" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "Alphabetical" apply: "Apply" asNumbers: "Sayılar olarak" + attempts: "Attempts" average: "Average first session length" averageActivePlaytime: "Ortalama Aktif Oyun Süresi" averageAfkTime: "Ortalama AFK Süresi" @@ -317,6 +321,7 @@ html: banned: "Yasaklanmış" bestPeak: "Tüm Zamanların Zirvesi" bestPing: "En iyi Ping" + blocked: "Blocked" calendar: " Takvim" comparing7days: "7 gün karşılaştırılıyor" connectionInfo: "Bağlantı Bilgisi" @@ -430,7 +435,10 @@ html: last24hours: "Son 24 saat" last30days: "Son 30 gün" last7days: "Son 7 gün" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Son bağlantı" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Son Zirve" lastSeen: "Son Görülme" latestJoinAddresses: "Latest Join Addresses" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_UK.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_UK.yml index 33a632252..106f931ff 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_UK.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_UK.yml @@ -294,9 +294,13 @@ html: afkTime: "Час AFK" all: "Всі" allTime: "Весь час" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "За алфавітом" apply: "Застосувати" asNumbers: "В числах" + attempts: "Attempts" average: "Середня тривалість першого сеансу" averageActivePlaytime: "Середній час активної гри" averageAfkTime: "Середній час AFK" @@ -317,6 +321,7 @@ html: banned: "Заблокований" bestPeak: "Максимальний Пік" bestPing: "Найкращий пінг" + blocked: "Blocked" calendar: "Календар" comparing7days: "Порівняння 7 днів" connectionInfo: "Інформація про з`єднання" @@ -430,7 +435,10 @@ html: last24hours: "Останні 24 години" last30days: "Останні 30 днів" last7days: "Останні 7 днів" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "Останнє підключення" + lastKnownAttempt: "Last Known Attempt" lastPeak: "Останній Пік" lastSeen: "Останнє відвідування" latestJoinAddresses: "Останні адреси приєднання" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/main/resources/assets/plan/locale/locale_ZH_TW.yml b/Plan/common/src/main/resources/assets/plan/locale/locale_ZH_TW.yml index 54ad45eb5..90d5c3f7f 100644 --- a/Plan/common/src/main/resources/assets/plan/locale/locale_ZH_TW.yml +++ b/Plan/common/src/main/resources/assets/plan/locale/locale_ZH_TW.yml @@ -294,9 +294,13 @@ html: afkTime: "掛機時間" all: "全部" allTime: "所有時間" + allowed: "Allowed" + allowlist: "Allowlist" + allowlistBounces: "Allowlist Bounces" alphabetical: "按字母順序" apply: "確定" asNumbers: "統計" + attempts: "Attempts" average: "Average first session length" averageActivePlaytime: "平均活躍時間" averageAfkTime: "平均掛機時間" @@ -317,6 +321,7 @@ html: banned: "已被封鎖" bestPeak: "所有時間峰值" bestPing: "最低延遲" + blocked: "Blocked" calendar: " 日誌" comparing7days: "對比 7 天的情況" connectionInfo: "連接訊息" @@ -430,7 +435,10 @@ html: last24hours: "過去 24 小時" last30days: "過去 30 天" last7days: "過去 7 天" + lastAllowed: "Last Allowed" + lastBlocked: "Last Blocked" lastConnected: "最後連接時間" + lastKnownAttempt: "Last Known Attempt" lastPeak: "上次線上峰值" lastSeen: "最後線上時間" latestJoinAddresses: "最後加入位址" @@ -686,6 +694,7 @@ html: page_player_sessions: "See Player Sessions -tab" page_player_versus: "See PvP & PvE -tab" page_server: "See all of server page" + page_server_allowlist_bounce: "See list of Game allowlist bounces" page_server_geolocations: "See Geolocations tab" page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_ping_per_country: "See Ping Per Country table" diff --git a/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java b/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java index 28156c95f..dd19ffff7 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java @@ -163,7 +163,8 @@ class AccessControlTest { Arguments.of("/v1/preferences", WebPermission.ACCESS, 200, 200), Arguments.of("/v1/storePreferences", WebPermission.ACCESS, 400, 400), Arguments.of("/v1/pluginHistory?server=" + TestConstants.SERVER_UUID_STRING, WebPermission.PAGE_NETWORK_PLUGIN_HISTORY, 200, 403), - Arguments.of("/v1/pluginHistory?server=" + TestConstants.SERVER_UUID_STRING, WebPermission.PAGE_SERVER_PLUGIN_HISTORY, 200, 403) + Arguments.of("/v1/pluginHistory?server=" + TestConstants.SERVER_UUID_STRING, WebPermission.PAGE_SERVER_PLUGIN_HISTORY, 200, 403), + Arguments.of("/v1/gameAllowlistBounces?server=" + TestConstants.SERVER_UUID_STRING, WebPermission.PAGE_SERVER_ALLOWLIST_BOUNCE, 200, 403) ); } diff --git a/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlVisibilityTest.java b/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlVisibilityTest.java index 5a50466a5..3feaa34ec 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlVisibilityTest.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlVisibilityTest.java @@ -136,6 +136,7 @@ class AccessControlVisibilityTest { Arguments.arguments(WebPermission.PAGE_SERVER_PLAYER_VERSUS_OVERVIEW, "pvp-pve-as-numbers", "pvppve"), Arguments.arguments(WebPermission.PAGE_SERVER_PLAYER_VERSUS_OVERVIEW, "pvp-pve-insights", "pvppve"), Arguments.arguments(WebPermission.PAGE_SERVER_PLAYER_VERSUS_KILL_LIST, "pvp-kills-table", "pvppve"), + Arguments.arguments(WebPermission.PAGE_SERVER_ALLOWLIST_BOUNCE, "allowlist-bounce-table", "allowlist"), Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_OVERVIEW, "playerbase-trends", "playerbase"), Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_OVERVIEW, "playerbase-insights", "playerbase"), Arguments.arguments(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS, "playerbase-graph", "playerbase"), diff --git a/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTestAggregate.java b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTestAggregate.java index 33458fdda..bd61dc157 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTestAggregate.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/DatabaseTestAggregate.java @@ -28,6 +28,7 @@ import com.djrapitops.plan.storage.database.transactions.patches.BadJoinAddressD public interface DatabaseTestAggregate extends ActivityIndexQueriesTest, + AllowlistQueriesTest, DatabaseBackupTest, ExtensionsDatabaseTest, GeolocationQueriesTest, diff --git a/Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/AllowlistQueriesTest.java b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/AllowlistQueriesTest.java new file mode 100644 index 000000000..7075b1d0d --- /dev/null +++ b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/AllowlistQueriesTest.java @@ -0,0 +1,63 @@ +/* + * 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 . + */ +package com.djrapitops.plan.storage.database.queries; + +import com.djrapitops.plan.delivery.domain.datatransfer.AllowlistBounce; +import com.djrapitops.plan.storage.database.DatabaseTestPreparer; +import com.djrapitops.plan.storage.database.queries.objects.AllowlistQueries; +import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction; +import com.djrapitops.plan.storage.database.transactions.events.StoreAllowlistBounceTransaction; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import utilities.TestConstants; + +import java.util.List; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public interface AllowlistQueriesTest extends DatabaseTestPreparer { + + @Test + @DisplayName("plan_allowlist_bounce is empty") + default void allowListTableIsEmpty() { + List expected = List.of(); + List result = db().query(AllowlistQueries.getBounces(serverUUID())); + assertEquals(expected, result); + } + + @Test + @DisplayName("plan_allowlist_bounce is cleared by RemoveEverythingTransaction") + default void allowListTableIsEmptyAfterClear() throws ExecutionException, InterruptedException { + allowListBounceIsStored(); + db().executeTransaction(new RemoveEverythingTransaction()); + allowListTableIsEmpty(); + } + + @Test + @DisplayName("Allowlist bounce is stored") + default void allowListBounceIsStored() throws ExecutionException, InterruptedException { + AllowlistBounce bounce = new AllowlistBounce(TestConstants.PLAYER_ONE_UUID, TestConstants.PLAYER_ONE_NAME, 1, System.currentTimeMillis()); + db().executeTransaction(new StoreAllowlistBounceTransaction(bounce.getPlayerUUID(), bounce.getPlayerName(), serverUUID(), bounce.getLastTime())) + .get(); + + List expected = List.of(bounce); + List result = db().query(AllowlistQueries.getBounces(serverUUID())); + assertEquals(expected, result); + } + +} \ No newline at end of file diff --git a/Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/SessionQueriesTest.java b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/SessionQueriesTest.java index 2bbd7d367..d2f40c842 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/SessionQueriesTest.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/SessionQueriesTest.java @@ -37,6 +37,7 @@ import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythi import com.djrapitops.plan.storage.database.transactions.events.*; import com.djrapitops.plan.utilities.java.Maps; import net.playeranalytics.plugin.scheduling.TimeAmount; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import utilities.RandomData; @@ -485,4 +486,21 @@ public interface SessionQueriesTest extends DatabaseTestPreparer { Map results = db().query(SessionQueries.playtimePerServer(Long.MIN_VALUE, Long.MAX_VALUE)); assertEquals(expected, results); } + + @Test + @DisplayName("Last seen query by server uuid groups last seen by player") + default void lastSeenByServerIsGroupedByPlayer() { + prepareForSessionSave(); + List player1Sessions = RandomData.randomSessions(serverUUID(), worlds, playerUUID, player2UUID); + List player2Sessions = RandomData.randomSessions(serverUUID(), worlds, player2UUID, playerUUID); + player1Sessions.forEach(session -> db().executeTransaction(new StoreSessionTransaction(session))); + player2Sessions.forEach(session -> db().executeTransaction(new StoreSessionTransaction(session))); + + long lastSeenP1 = new SessionsMutator(player1Sessions).toLastSeen(); + long lastSeenP2 = new SessionsMutator(player2Sessions).toLastSeen(); + + Map expected = Map.of(playerUUID, lastSeenP1, player2UUID, lastSeenP2); + Map result = db().query(SessionQueries.lastSeen(serverUUID())); + assertEquals(expected, result); + } } diff --git a/Plan/nukkit/src/main/java/com/djrapitops/plan/gathering/listeners/nukkit/PlayerOnlineListener.java b/Plan/nukkit/src/main/java/com/djrapitops/plan/gathering/listeners/nukkit/PlayerOnlineListener.java index 572240351..67a524b93 100644 --- a/Plan/nukkit/src/main/java/com/djrapitops/plan/gathering/listeners/nukkit/PlayerOnlineListener.java +++ b/Plan/nukkit/src/main/java/com/djrapitops/plan/gathering/listeners/nukkit/PlayerOnlineListener.java @@ -35,6 +35,7 @@ import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction; import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction; +import com.djrapitops.plan.storage.database.transactions.events.StoreAllowlistBounceTransaction; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; @@ -95,6 +96,13 @@ public class PlayerOnlineListener implements Listener { @EventHandler(priority = EventPriority.MONITOR) public void onPlayerKick(PlayerKickEvent event) { try { + if (event.getReasonEnum() == PlayerKickEvent.Reason.NOT_WHITELISTED) { + dbSystem.getDatabase().executeTransaction(new StoreAllowlistBounceTransaction( + event.getPlayer().getUniqueId(), + event.getPlayer().getName(), + serverInfo.getServerUUID(), System.currentTimeMillis()) + ); + } if (status.areKicksNotCounted() || event.isCancelled()) { return; } diff --git a/Plan/react/dashboard/src/App.jsx b/Plan/react/dashboard/src/App.jsx index c90e8b574..22267b4fd 100644 --- a/Plan/react/dashboard/src/App.jsx +++ b/Plan/react/dashboard/src/App.jsx @@ -32,6 +32,7 @@ const ServerOverview = React.lazy(() => import("./views/server/ServerOverview")) const OnlineActivity = React.lazy(() => import("./views/server/OnlineActivity")); const ServerSessions = React.lazy(() => import("./views/server/ServerSessions")); const ServerPvpPve = React.lazy(() => import("./views/server/ServerPvpPve")); +const ServerAllowList = React.lazy(() => import("./views/server/ServerAllowList")); const PlayerbaseOverview = React.lazy(() => import("./views/server/PlayerbaseOverview")); const ServerPlayers = React.lazy(() => import("./views/server/ServerPlayers")); const ServerGeolocations = React.lazy(() => import("./views/server/ServerGeolocations")); @@ -159,6 +160,7 @@ function App() { }/> }/> }/> + }/> }/> }/> }/> diff --git a/Plan/react/dashboard/src/components/cards/server/tables/AllowlistBounceTableCard.jsx b/Plan/react/dashboard/src/components/cards/server/tables/AllowlistBounceTableCard.jsx new file mode 100644 index 000000000..94b2bafb3 --- /dev/null +++ b/Plan/react/dashboard/src/components/cards/server/tables/AllowlistBounceTableCard.jsx @@ -0,0 +1,22 @@ +import React from "react"; +import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome"; +import {faFilterCircleXmark} from "@fortawesome/free-solid-svg-icons"; +import {Card} from "react-bootstrap"; +import AllowlistBounceTable from "../../../table/AllowlistBounceTable.jsx"; +import {useTranslation} from "react-i18next"; + +const AllowlistBounceTableCard = ({bounces, lastSeen}) => { + const {t} = useTranslation(); + return ( + + +
+ {t('html.label.allowlistBounces')} +
+
+ +
+ ) +}; + +export default AllowlistBounceTableCard; \ No newline at end of file diff --git a/Plan/react/dashboard/src/components/modal/QueryPlayerListModal.jsx b/Plan/react/dashboard/src/components/modal/QueryPlayerListModal.jsx index e53a5d704..59ede02fd 100644 --- a/Plan/react/dashboard/src/components/modal/QueryPlayerListModal.jsx +++ b/Plan/react/dashboard/src/components/modal/QueryPlayerListModal.jsx @@ -25,10 +25,11 @@ const QueryPlayerListModal = ({open, toggle, queryData, title}) => { } - {hasPermission('access.query') && Boolean(queryData?.data?.players.players.length) && - {t('html.query.label.showFullQuery')} - } + {hasPermission('access.query') && Boolean(queryData?.data?.players.players.length) && + + {t('html.query.label.showFullQuery')} + } diff --git a/Plan/react/dashboard/src/components/table/AllowlistBounceTable.jsx b/Plan/react/dashboard/src/components/table/AllowlistBounceTable.jsx new file mode 100644 index 000000000..192437287 --- /dev/null +++ b/Plan/react/dashboard/src/components/table/AllowlistBounceTable.jsx @@ -0,0 +1,72 @@ +import React, {useCallback} from "react"; +import {useTranslation} from "react-i18next"; +import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome"; +import {faDoorOpen, faRepeat, faUser} from "@fortawesome/free-solid-svg-icons"; +import {usePreferences} from "../../hooks/preferencesHook.jsx"; +import DataTablesTable from "./DataTablesTable.jsx"; +import {formatDate, useDatePreferences} from "../text/FormattedDate.jsx"; +import {faCalendarCheck, faCalendarTimes} from "@fortawesome/free-regular-svg-icons"; +import {Link} from "react-router-dom"; + +const AllowlistBounceTable = ({bounces, lastSeen}) => { + const {t} = useTranslation(); + const {preferencesLoaded} = usePreferences(); + + const datePreferences = useDatePreferences(); + const formatDateEasy = date => { + return formatDate(date, datePreferences.offset, datePreferences.pattern, false, datePreferences.recentDaysPattern, t); + } + + const columns = [{ + title: <> {t('html.label.player')}, + data: {_: "player", display: "link"} + }, { + title: <> {t('html.label.attempts')}, + data: "attempts" + }, { + title: <> {t('html.label.lastKnownAttempt')}, + data: "lastKnownAttempt" + }, { + title: <> {t('html.label.lastBlocked')}, + data: {_: "date", display: "dateFormatted"} + }, { + title: <> {t('html.label.lastAllowed')}, + data: {_: "lastSeen", display: "lastSeenFormatted"} + }]; + + const rows = bounces.map(bounce => { + const seenAfterBounce = bounce.lastBounce < lastSeen[bounce.playerUUID]; + const playerId = bounce.playerName + ' / ' + bounce.playerUUID; + return { + player: playerId, + link: lastSeen[bounce.playerUUID] ? {playerId} : playerId, + date: bounce.lastTime, + dateFormatted: formatDateEasy(bounce.lastTime), + attempts: bounce.count, + lastKnownAttempt: seenAfterBounce ? t('html.label.allowed') : t('html.label.blocked'), + lastSeen: lastSeen[bounce.playerUUID], + lastSeenFormatted: formatDateEasy(lastSeen[bounce.playerUUID]) + }; + }); + const options = { + responsive: true, + deferRender: true, + columns: columns, + data: rows, + paginationCount: 2, + order: [[1, "desc"]] + } + + const rowKeyFunction = useCallback((row, column) => { + return row.player + "-" + (column ? JSON.stringify(column.data) : ''); + }, []); + + if (!preferencesLoaded) return <>; + + return ( + + ) +}; + +export default AllowlistBounceTable; \ No newline at end of file diff --git a/Plan/react/dashboard/src/service/serverService.js b/Plan/react/dashboard/src/service/serverService.js index f60fd9650..7a77befe8 100644 --- a/Plan/react/dashboard/src/service/serverService.js +++ b/Plan/react/dashboard/src/service/serverService.js @@ -100,6 +100,7 @@ export const fetchPlayersTable = async (timestamp, identifier) => { return await fetchPlayersTableNetwork(timestamp); } } + const fetchPlayersTableServer = async (timestamp, identifier) => { let url = `/v1/playersTable?server=${identifier}`; if (staticSite) url = `/data/playersTable-${identifier}.json`; @@ -112,6 +113,12 @@ const fetchPlayersTableNetwork = async (timestamp) => { return doGetRequest(url, timestamp); } +export const fetchAllowlistBounces = async (timestamp, identifier) => { + let url = `/v1/gameAllowlistBounces?server=${identifier}`; + if (staticSite) url = `/data/gameAllowlistBounces-${identifier}.json`; + return doGetRequest(url, timestamp); +} + export const fetchPingTable = async (timestamp, identifier) => { let url = `/v1/pingTable?server=${identifier}`; if (staticSite) url = `/data/pingTable-${identifier}.json`; diff --git a/Plan/react/dashboard/src/views/layout/ServerPage.jsx b/Plan/react/dashboard/src/views/layout/ServerPage.jsx index fbb2f1c14..a567b000c 100644 --- a/Plan/react/dashboard/src/views/layout/ServerPage.jsx +++ b/Plan/react/dashboard/src/views/layout/ServerPage.jsx @@ -9,6 +9,7 @@ import { faCodeCompare, faCogs, faCubes, + faFilterCircleXmark, faGlobe, faInfoCircle, faLocationArrow, @@ -72,6 +73,12 @@ const ServerSidebar = () => { icon: faCampground, href: "pvppve", permission: 'page.server.player.versus' + }, + { + name: 'html.label.allowlist', + icon: faFilterCircleXmark, + href: "allowlist", + permission: 'page.server.allowlist.bounce' } ], }, diff --git a/Plan/react/dashboard/src/views/server/ServerAllowList.jsx b/Plan/react/dashboard/src/views/server/ServerAllowList.jsx new file mode 100644 index 000000000..5ebdb07da --- /dev/null +++ b/Plan/react/dashboard/src/views/server/ServerAllowList.jsx @@ -0,0 +1,35 @@ +import React from 'react'; +import {useDataRequest} from "../../hooks/dataFetchHook"; +import {useParams} from "react-router-dom"; +import {fetchAllowlistBounces} from "../../service/serverService"; +import ErrorView from "../ErrorView"; +import {Col} from "react-bootstrap"; +import LoadIn from "../../components/animation/LoadIn"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; +import {useAuth} from "../../hooks/authenticationHook"; +import AllowlistBounceTableCard from "../../components/cards/server/tables/AllowlistBounceTableCard.jsx"; + +const ServerAllowList = () => { + const {hasPermission} = useAuth(); + const {identifier} = useParams(); + + const seeBounce = hasPermission('page.server.allowlist.bounce'); + const {data, loadingError} = useDataRequest(fetchAllowlistBounces, [identifier], seeBounce); + + if (loadingError) return + + return ( + +
+ {seeBounce && + + + + } +
+
+ ) +}; + +export default ServerAllowList \ No newline at end of file diff --git a/Plan/sponge/src/main/java/com/djrapitops/plan/gathering/listeners/sponge/PlayerOnlineListener.java b/Plan/sponge/src/main/java/com/djrapitops/plan/gathering/listeners/sponge/PlayerOnlineListener.java index 49ffca1bc..bea3012fc 100644 --- a/Plan/sponge/src/main/java/com/djrapitops/plan/gathering/listeners/sponge/PlayerOnlineListener.java +++ b/Plan/sponge/src/main/java/com/djrapitops/plan/gathering/listeners/sponge/PlayerOnlineListener.java @@ -27,8 +27,10 @@ import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction; import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction; +import com.djrapitops.plan.storage.database.transactions.events.StoreAllowlistBounceTransaction; import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorLogger; +import org.spongepowered.api.Game; import org.spongepowered.api.Sponge; import org.spongepowered.api.entity.living.player.Player; import org.spongepowered.api.entity.living.player.server.ServerPlayer; @@ -54,6 +56,7 @@ public class PlayerOnlineListener { private final PlayerJoinEventConsumer joinEventConsumer; private final PlayerLeaveEventConsumer leaveEventConsumer; + private final Game game; private final ServerInfo serverInfo; private final DBSystem dbSystem; private final Status status; @@ -63,13 +66,14 @@ public class PlayerOnlineListener { public PlayerOnlineListener( PlayerJoinEventConsumer joinEventConsumer, PlayerLeaveEventConsumer leaveEventConsumer, - ServerInfo serverInfo, + Game game, ServerInfo serverInfo, DBSystem dbSystem, Status status, ErrorLogger errorLogger ) { this.joinEventConsumer = joinEventConsumer; this.leaveEventConsumer = leaveEventConsumer; + this.game = game; this.serverInfo = serverInfo; this.dbSystem = dbSystem; this.status = status; @@ -89,6 +93,18 @@ public class PlayerOnlineListener { GameProfile profile = event.profile(); UUID playerUUID = profile.uniqueId(); ServerUUID serverUUID = serverInfo.getServerUUID(); + if (game.server().isWhitelistEnabled()) { + game.server().serviceProvider().whitelistService().isWhitelisted(profile) + .thenAccept(whitelisted -> { + if (Boolean.FALSE.equals(whitelisted)) { + dbSystem.getDatabase().executeTransaction(new StoreAllowlistBounceTransaction( + playerUUID, + event.profile().name().orElse(event.user().uniqueId().toString()), + serverUUID, + System.currentTimeMillis())); + } + }); + } dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, () -> isBanned(profile))); }