Merge branch 'master' into 2233/whitelist-bounce-gathering
# Conflicts: # Plan/common/src/main/java/com/djrapitops/plan/storage/database/transactions/init/CreateTablesTransaction.java
This commit is contained in:
commit
3ebe7a66a1
|
@ -23,30 +23,16 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: 📥 Checkout git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: ☕ Setup JDK
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '17'
|
||||
- name: 🚦 Setup Selenium Webdriver
|
||||
uses: nanasess/setup-chromedriver@v2
|
||||
- name: 🚦 Setup Selenium Webdriver settings
|
||||
run: |
|
||||
export DISPLAY=:99
|
||||
chromedriver --url-base=/wd/hub &
|
||||
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||
- name: 📶 Verify MariaDB connection
|
||||
env:
|
||||
PORT: ${{ job.services.mariadb.ports[3306] }}
|
||||
run: |
|
||||
while ! mysqladmin ping -h"127.0.0.1" -P"$PORT" --silent; do
|
||||
sleep 1
|
||||
done
|
||||
- name: 💼 Load Gradle Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
@ -68,15 +54,29 @@ jobs:
|
|||
echo "versionString=$(cat build/versions/jar.txt)" >> $GITHUB_ENV
|
||||
echo "artifactPath=$(pwd)/builds" >> $GITHUB_ENV
|
||||
- name: 📤 Upload Plan.jar
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Plan-${{ env.versionString }}-${{ env.git_hash }}.jar
|
||||
path: ${{ env.artifactPath }}/Plan-${{ env.snapshotVersion }}.jar
|
||||
- name: 📤 Upload PlanFabric.jar
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PlanFabric-${{ env.versionString }}-${{ env.git_hash }}.jar
|
||||
path: ${{ env.artifactPath }}/PlanFabric-${{ env.snapshotVersion }}.jar
|
||||
- name: 🚦 Setup Selenium Webdriver
|
||||
uses: nanasess/setup-chromedriver@v2
|
||||
- name: 🚦 Setup Selenium Webdriver settings
|
||||
run: |
|
||||
export DISPLAY=:99
|
||||
chromedriver --url-base=/wd/hub &
|
||||
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||
- name: 📶 Verify MariaDB connection
|
||||
env:
|
||||
PORT: ${{ job.services.mariadb.ports[3306] }}
|
||||
run: |
|
||||
while ! mysqladmin ping -h"127.0.0.1" -P"$PORT" --silent; do
|
||||
sleep 1
|
||||
done
|
||||
- name: 🩺 Test
|
||||
env:
|
||||
MYSQL_DB: test
|
||||
|
|
|
@ -64,6 +64,9 @@ public interface CapabilityService {
|
|||
return Capability.getByName(capabilityName).isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton holder for listeners.
|
||||
*/
|
||||
class ListHolder {
|
||||
static final AtomicReference<List<Consumer<Boolean>>> enableListeners = new AtomicReference<>(
|
||||
new CopyOnWriteArrayList<>()
|
||||
|
|
|
@ -89,7 +89,7 @@ public interface ComponentService {
|
|||
* Converts the given input into a {@link Component}.
|
||||
* Input example {@code §ctext}.
|
||||
*
|
||||
* @param legacy the input legacy
|
||||
* @param legacy the input legacy
|
||||
* @param character the character to use as the color prefix, usually {@code §}.
|
||||
* @return a {@link Component}
|
||||
* @see #fromLegacy(String)
|
||||
|
@ -115,7 +115,7 @@ public interface ComponentService {
|
|||
* Input example: {@code &#rrggbbtext}.
|
||||
*
|
||||
* @param adventureLegacy the input adventure legacy
|
||||
* @param character the character to use as the color prefix, usually {@code &}.
|
||||
* @param character the character to use as the color prefix, usually {@code &}.
|
||||
* @return a {@link Component}
|
||||
* @see #fromAdventureLegacy(String)
|
||||
* @see Component#SECTION
|
||||
|
@ -139,7 +139,7 @@ public interface ComponentService {
|
|||
* Input example: {@code §x§r§r§g§g§b§btext}.
|
||||
*
|
||||
* @param bungeeLegacy the input bungee legacy
|
||||
* @param character the character to use as the color prefix, usually {@code §}.
|
||||
* @param character the character to use as the color prefix, usually {@code §}.
|
||||
* @return a {@link Component}
|
||||
* @see Component#SECTION
|
||||
* @see Component#AMPERSAND
|
||||
|
@ -164,6 +164,9 @@ public interface ComponentService {
|
|||
*/
|
||||
Component fromJson(String json);
|
||||
|
||||
/**
|
||||
* Singleton holder for ComponentService.
|
||||
*/
|
||||
class Holder {
|
||||
static final AtomicReference<ComponentService> service = new AtomicReference<>();
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/**
|
||||
* PageExtension API for extending the webserver and the website.
|
||||
*
|
||||
* <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5-PageExtension-API">Documentation</a>
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.web;
|
|
@ -46,6 +46,11 @@ public final class CompositeResolver implements Resolver {
|
|||
this.resolvers = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new builder for a .
|
||||
*
|
||||
* @return a builder.
|
||||
*/
|
||||
public static CompositeResolver.Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
@ -100,6 +105,9 @@ public final class CompositeResolver implements Resolver {
|
|||
return getResolver(forThis.getPath()).map(resolver -> resolver.requiresAuth(forThis)).orElse(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for {@link CompositeResolver}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private final CompositeResolver composite;
|
||||
|
||||
|
@ -132,6 +140,11 @@ public final class CompositeResolver implements Resolver {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the final result after adding all resolvers.
|
||||
*
|
||||
* @return The {@link CompositeResolver}
|
||||
*/
|
||||
public CompositeResolver build() {
|
||||
return composite;
|
||||
}
|
||||
|
|
|
@ -22,11 +22,20 @@ import java.util.Optional;
|
|||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Utility class for constructing a {@link Resolver} with Functional Interfaces.
|
||||
*/
|
||||
public class FunctionalResolverWrapper implements Resolver {
|
||||
|
||||
private final Function<Request, Optional<Response>> resolver;
|
||||
private final Predicate<Request> accessCheck;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
* @param resolver Function that solves the {@link Request} into an Optional {@link Response}.
|
||||
* @param accessCheck Predicate that checks if {@link Request} is allowed or if 403 Forbidden should be given.
|
||||
*/
|
||||
public FunctionalResolverWrapper(Function<Request, Optional<Response>> resolver, Predicate<Request> accessCheck) {
|
||||
this.resolver = resolver;
|
||||
this.accessCheck = accessCheck;
|
||||
|
|
|
@ -19,7 +19,9 @@ package com.djrapitops.plan.delivery.web.resolver;
|
|||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Interface for resolving requests of Plan webserver.
|
||||
|
@ -40,6 +42,25 @@ public interface Resolver {
|
|||
*/
|
||||
boolean canAccess(Request request);
|
||||
|
||||
/**
|
||||
* Override this to tell Plan what web permissions this endpoint uses.
|
||||
* <p>
|
||||
* This allows:
|
||||
* <ul>
|
||||
* <li>Plan to store these permissions in the permission list</li>
|
||||
* <li>Users can grant/deny the permission for a group</li>
|
||||
* <li>Plan can show what endpoints specific permission gives access to</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Requires PAGE_EXTENSION_USER_PERMISSIONS capability
|
||||
*
|
||||
* @return Set of permissions eg. [plugin.custom.permission, plugin.custom.permission.child.node]
|
||||
* @see com.djrapitops.plan.capability.CapabilityService for Capability checks
|
||||
*/
|
||||
default Set<String> usedWebPermissions() {
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement request resolution.
|
||||
*
|
||||
|
@ -51,10 +72,21 @@ public interface Resolver {
|
|||
*/
|
||||
Optional<Response> resolve(Request request);
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResponseBuilder} for a {@link Response}.
|
||||
*
|
||||
* @return a new builder.
|
||||
*/
|
||||
default ResponseBuilder newResponseBuilder() {
|
||||
return Response.builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to check if the resolver requires authentication to be used.
|
||||
*
|
||||
* @param request Incoming request that you can use to figure out if authentication is required.
|
||||
* @return true if you want 401 to be given when user has not logged in.
|
||||
*/
|
||||
default boolean requiresAuth(Request request) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ public class ResponseBuilder {
|
|||
/**
|
||||
* Set MIME Type of the Response.
|
||||
*
|
||||
* @param mimeType MIME type of the Response https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
|
||||
* @param mimeType MIME type of the Response <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types">Documentation</a>
|
||||
* @return this builder.
|
||||
* @see MimeType for common MIME types.
|
||||
*/
|
||||
|
@ -46,7 +46,7 @@ public class ResponseBuilder {
|
|||
* <p>
|
||||
* Default status code is 200 (OK) if not set.
|
||||
*
|
||||
* @param code 1xx, 2xx, 3xx, 4xx, 5xx, https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
||||
* @param code 1xx, 2xx, 3xx, 4xx, 5xx, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">Documentation</a>
|
||||
* @return this builder.
|
||||
*/
|
||||
public ResponseBuilder setStatus(int code) {
|
||||
|
@ -57,7 +57,7 @@ public class ResponseBuilder {
|
|||
/**
|
||||
* Set HTTP Response header.
|
||||
*
|
||||
* @param header Key of the header. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
|
||||
* @param header Key of the header. <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers">Documentation</a>
|
||||
* @param value Value for the header.
|
||||
* @return this builder.
|
||||
*/
|
||||
|
@ -75,7 +75,7 @@ public class ResponseBuilder {
|
|||
* Utility method for building redirects.
|
||||
*
|
||||
* @param url URL to redirect the client to with 302 Found.
|
||||
* @return https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location
|
||||
* @return <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location">Documentation</a>
|
||||
*/
|
||||
public ResponseBuilder redirectTo(String url) {
|
||||
return setStatus(302).setHeader("Location", url).setContent(new byte[0]);
|
||||
|
|
|
@ -26,6 +26,11 @@ package com.djrapitops.plan.delivery.web.resolver.exception;
|
|||
*/
|
||||
public class BadRequestException extends IllegalArgumentException {
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
* @param message Error message - avoid including any input incoming in the request to prevent XSS.
|
||||
*/
|
||||
public BadRequestException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/**
|
||||
* Classes for implementing functionality with {@link com.djrapitops.plan.delivery.web.ResolverService}.
|
||||
*
|
||||
* <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5-PageExtension-API#resolverservice">Documentation</a>
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.web.resolver;
|
|
@ -43,7 +43,7 @@ public final class Request {
|
|||
* @param path Requested path /example/target
|
||||
* @param query Request parameters ?param=value etc
|
||||
* @param user Web user doing the request (if authenticated)
|
||||
* @param headers Request headers https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
|
||||
* @param headers Request headers <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers">Documentation</a>
|
||||
* @param requestBody Raw body as bytes, if present
|
||||
*/
|
||||
public Request(String method, URIPath path, URIQuery query, WebUser user, Map<String, String> headers, byte[] requestBody) {
|
||||
|
@ -57,6 +57,11 @@ public final class Request {
|
|||
|
||||
/**
|
||||
* Special constructor that figures out URIPath and URIQuery from "/path/and?query=params" and has no request body.
|
||||
*
|
||||
* @param method HTTP requst method
|
||||
* @param target The requested path and query, e.g. "/path/and?query=params"
|
||||
* @param user User that made the request
|
||||
* @param headers HTTP request headers
|
||||
*/
|
||||
public Request(String method, String target, WebUser user, Map<String, String> headers) {
|
||||
this.method = method;
|
||||
|
@ -121,7 +126,7 @@ public final class Request {
|
|||
/**
|
||||
* Get a header in the request.
|
||||
*
|
||||
* @param key https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
|
||||
* @param key <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers">Documentation</a>
|
||||
* @return Value if it is present in the request.
|
||||
*/
|
||||
public Optional<String> getHeader(String key) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/**
|
||||
* Classes for implementing functionality with {@link com.djrapitops.plan.delivery.web.ResourceService}.
|
||||
*
|
||||
* <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5-PageExtension-API#resourceservice">Documentation</a>
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.web.resource;
|
|
@ -113,6 +113,7 @@ public interface DataExtension {
|
|||
* <p>
|
||||
* Requires Capability DATA_EXTENSION_BUILDER_API
|
||||
*
|
||||
* @param text Text that is displayed next to the value.
|
||||
* @return new builder.
|
||||
*/
|
||||
default ValueBuilder valueBuilder(String text) {
|
||||
|
|
|
@ -81,6 +81,9 @@ public interface ExtensionService {
|
|||
*/
|
||||
void unregister(DataExtension extension);
|
||||
|
||||
/**
|
||||
* Singleton holder for {@link ExtensionService}.
|
||||
*/
|
||||
class Holder {
|
||||
static final AtomicReference<ExtensionService> service = new AtomicReference<>();
|
||||
|
||||
|
|
|
@ -42,6 +42,12 @@ public enum FormatType {
|
|||
*/
|
||||
NONE;
|
||||
|
||||
/**
|
||||
* Get a format type by the enum name without exception.
|
||||
*
|
||||
* @param name FormatType#name()
|
||||
* @return Optional if the format type is found by that name, empty if not found.
|
||||
*/
|
||||
public static Optional<FormatType> getByName(String name) {
|
||||
if (name == null) {
|
||||
return Optional.empty();
|
||||
|
@ -51,4 +57,5 @@ public enum FormatType {
|
|||
} catch (IllegalArgumentException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,11 @@ package com.djrapitops.plan.extension;
|
|||
*/
|
||||
public interface Group {
|
||||
|
||||
/**
|
||||
* Get the name of the group.
|
||||
*
|
||||
* @return Name of the group given by a {@link com.djrapitops.plan.extension.annotation.GroupProvider}, e.g. "Miner"
|
||||
*/
|
||||
String getGroupName();
|
||||
|
||||
}
|
|
@ -89,7 +89,7 @@ public @interface BooleanProvider {
|
|||
/**
|
||||
* Name of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Name of the icon, if name is not valid no icon is shown.
|
||||
*/
|
||||
|
@ -98,7 +98,7 @@ public @interface BooleanProvider {
|
|||
/**
|
||||
* Family of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
|
||||
*/
|
||||
|
|
|
@ -76,7 +76,7 @@ public @interface ComponentProvider {
|
|||
/**
|
||||
* Name of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Name of the icon, if name is not valid no icon is shown.
|
||||
*/
|
||||
|
@ -85,7 +85,7 @@ public @interface ComponentProvider {
|
|||
/**
|
||||
* Family of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
|
||||
*/
|
||||
|
|
|
@ -67,7 +67,7 @@ public @interface DoubleProvider {
|
|||
/**
|
||||
* Name of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Name of the icon, if name is not valid no icon is shown.
|
||||
*/
|
||||
|
@ -76,7 +76,7 @@ public @interface DoubleProvider {
|
|||
/**
|
||||
* Family of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
|
||||
*/
|
||||
|
|
|
@ -62,7 +62,7 @@ public @interface GroupProvider {
|
|||
/**
|
||||
* Name of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Name of the icon, if name is not valid no icon is shown.
|
||||
*/
|
||||
|
@ -71,7 +71,7 @@ public @interface GroupProvider {
|
|||
/**
|
||||
* Family of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
|
||||
*/
|
||||
|
|
|
@ -39,10 +39,18 @@ public @interface InvalidateMethod {
|
|||
*/
|
||||
String value();
|
||||
|
||||
/**
|
||||
* Multiple {@link InvalidateMethod} annotations are supported per class.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@interface Multiple {
|
||||
|
||||
/**
|
||||
* All the annotations.
|
||||
*
|
||||
* @return All InvalidateMethod annotations in the class.
|
||||
*/
|
||||
InvalidateMethod[] value();
|
||||
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ public @interface NumberProvider {
|
|||
/**
|
||||
* Name of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Name of the icon, if name is not valid no icon is shown.
|
||||
*/
|
||||
|
@ -88,7 +88,7 @@ public @interface NumberProvider {
|
|||
/**
|
||||
* Family of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
|
||||
*/
|
||||
|
|
|
@ -70,7 +70,7 @@ public @interface PercentageProvider {
|
|||
/**
|
||||
* Name of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Name of the icon, if name is not valid no icon is shown.
|
||||
*/
|
||||
|
@ -79,7 +79,7 @@ public @interface PercentageProvider {
|
|||
/**
|
||||
* Family of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
|
||||
*/
|
||||
|
|
|
@ -44,7 +44,7 @@ public @interface PluginInfo {
|
|||
/**
|
||||
* Name of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Name of the icon, if name is not valid no icon is shown.
|
||||
*/
|
||||
|
@ -53,7 +53,7 @@ public @interface PluginInfo {
|
|||
/**
|
||||
* Family of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
|
||||
*/
|
||||
|
|
|
@ -79,7 +79,7 @@ public @interface StringProvider {
|
|||
/**
|
||||
* Name of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Name of the icon, if name is not valid no icon is shown.
|
||||
*/
|
||||
|
@ -88,7 +88,7 @@ public @interface StringProvider {
|
|||
/**
|
||||
* Family of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
|
||||
*/
|
||||
|
|
|
@ -41,7 +41,7 @@ public @interface TabInfo {
|
|||
/**
|
||||
* Name of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Name of the icon, if name is not valid no icon is shown.
|
||||
*/
|
||||
|
@ -50,7 +50,7 @@ public @interface TabInfo {
|
|||
/**
|
||||
* Family of Font Awesome icon.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @return Family that matches an icon, if there is no icon for this family no icon is shown.
|
||||
*/
|
||||
|
|
|
@ -18,7 +18,10 @@ package com.djrapitops.plan.extension.builder;
|
|||
|
||||
import com.djrapitops.plan.component.Component;
|
||||
import com.djrapitops.plan.extension.FormatType;
|
||||
import com.djrapitops.plan.extension.annotation.*;
|
||||
import com.djrapitops.plan.extension.annotation.BooleanProvider;
|
||||
import com.djrapitops.plan.extension.annotation.Conditional;
|
||||
import com.djrapitops.plan.extension.annotation.StringProvider;
|
||||
import com.djrapitops.plan.extension.annotation.Tab;
|
||||
import com.djrapitops.plan.extension.extractor.ExtensionMethod;
|
||||
import com.djrapitops.plan.extension.icon.Color;
|
||||
import com.djrapitops.plan.extension.icon.Family;
|
||||
|
@ -64,7 +67,7 @@ public interface ValueBuilder {
|
|||
/**
|
||||
* Icon displayed next to the value.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons
|
||||
*
|
||||
* @param iconName Name of the icon
|
||||
* @param iconFamily Family of the icon
|
||||
|
@ -78,7 +81,7 @@ public interface ValueBuilder {
|
|||
/**
|
||||
* Icon displayed next to the value.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons
|
||||
*
|
||||
* @param icon Icon built using the methods in {@link Icon}.
|
||||
* @return This builder.
|
||||
|
|
|
@ -47,6 +47,12 @@ public enum Color {
|
|||
BLACK,
|
||||
NONE;
|
||||
|
||||
/**
|
||||
* Get a color by the enum name without exception.
|
||||
*
|
||||
* @param name Color#name()
|
||||
* @return Optional if the color is found by that name, empty if not found.
|
||||
*/
|
||||
public static Optional<Color> getByName(String name) {
|
||||
if (name == null) {
|
||||
return Optional.empty();
|
||||
|
@ -56,4 +62,5 @@ public enum Color {
|
|||
} catch (IllegalArgumentException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,12 @@ public enum Family {
|
|||
*/
|
||||
BRAND;
|
||||
|
||||
/**
|
||||
* Get a family by the enum name without exception.
|
||||
*
|
||||
* @param name Family#name()
|
||||
* @return Optional if the family is found by that name, empty if not found.
|
||||
*/
|
||||
public static Optional<Family> getByName(String name) {
|
||||
if (name == null) {
|
||||
return Optional.empty();
|
||||
|
|
|
@ -21,7 +21,7 @@ import java.util.Objects;
|
|||
/**
|
||||
* Object that represents an icon on the website.
|
||||
* <p>
|
||||
* See https://fontawesome.com/icons (select 'free')) for icons and their {@link Family}.
|
||||
* See <a href="https://fontawesome.com/icons">FontAwesome</a> (select 'free')) for icons and their {@link Family}.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
|
@ -92,6 +92,9 @@ public class Icon {
|
|||
return "Icon{" + type.name() + ", '" + name + '\'' + ", " + color.name() + '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for an {@link Icon}.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final Icon icon;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/**
|
||||
* DataExtension API and related classes.
|
||||
*
|
||||
* <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API">Documentation</a>
|
||||
*/
|
||||
package com.djrapitops.plan.extension;
|
|
@ -62,14 +62,44 @@ public interface CommonQueries {
|
|||
*/
|
||||
long fetchLastSeen(UUID playerUUID, UUID serverUUID);
|
||||
|
||||
/**
|
||||
* Get the UUIDs of all servers Plan has registered.
|
||||
*
|
||||
* @return Set of Server UUIDs
|
||||
*/
|
||||
Set<UUID> fetchServerUUIDs();
|
||||
|
||||
/**
|
||||
* Fetch UUID of a player by name.
|
||||
*
|
||||
* @param playerName Name of the player
|
||||
* @return UUID if it is found by Plan or empty if not found.
|
||||
*/
|
||||
Optional<UUID> fetchUUIDOf(String playerName);
|
||||
|
||||
/**
|
||||
* Fetch name of a player by UUID.
|
||||
*
|
||||
* @param playerUUID UUID of the player
|
||||
* @return Name if it is known by Plan or empty if not.
|
||||
*/
|
||||
Optional<String> fetchNameOf(UUID playerUUID);
|
||||
|
||||
/**
|
||||
* Check that schema has table you are using in your queries.
|
||||
*
|
||||
* @param table Name of the table, e.g. plan_users.
|
||||
* @return true if table exists.
|
||||
*/
|
||||
boolean doesDBHaveTable(String table);
|
||||
|
||||
/**
|
||||
* Check that schema table has a column you are using in your queries.
|
||||
*
|
||||
* @param table Name of the table, e.g. plan_users.
|
||||
* @param column Name of the column, e.g. id
|
||||
* @return true if table and column exist.
|
||||
*/
|
||||
boolean doesDBHaveTableColumn(String table, String column);
|
||||
|
||||
/**
|
||||
|
|
|
@ -126,7 +126,7 @@ public interface QueryService {
|
|||
CommonQueries getCommonQueries();
|
||||
|
||||
/**
|
||||
* See https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
|
||||
* See <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">Functional Interfaces</a>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface ThrowingConsumer<T> {
|
||||
|
@ -134,7 +134,7 @@ public interface QueryService {
|
|||
}
|
||||
|
||||
/**
|
||||
* See https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
|
||||
* See <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">Functional Interfaces</a>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface ThrowingFunction<T, R> {
|
||||
|
@ -142,7 +142,7 @@ public interface QueryService {
|
|||
}
|
||||
|
||||
/**
|
||||
* See https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
|
||||
* See <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">Functional Interfaces</a>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface VoidFunction {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/**
|
||||
* Query API related classes.
|
||||
*
|
||||
* <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5-Query-API">Documentation</a>
|
||||
*/
|
||||
package com.djrapitops.plan.query;
|
|
@ -51,6 +51,9 @@ public interface ListenerService {
|
|||
*/
|
||||
void registerListenerForPlan(Object listener);
|
||||
|
||||
/**
|
||||
* Singleton holder for listeners.
|
||||
*/
|
||||
class Holder {
|
||||
static final AtomicReference<ListenerService> service = new AtomicReference<>();
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ plugins {
|
|||
id 'java-library'
|
||||
id "jacoco"
|
||||
id "checkstyle"
|
||||
id "org.sonarqube" version "4.3.1.3277"
|
||||
id "org.sonarqube" version "4.4.1.3373"
|
||||
id 'fabric-loom' version '1.3-SNAPSHOT' apply false
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,8 @@ allprojects {
|
|||
ext.minorVersion = '6'
|
||||
ext.buildVersion = buildVersion
|
||||
ext.fullVersion = project.ext.majorVersion + '.' + project.ext.minorVersion + ' build ' + project.ext.buildVersion
|
||||
ext.fullVersionFilename = project.ext.majorVersion + '.' + project.ext.minorVersion + '-build-' + project.ext.buildVersion
|
||||
ext.fullVersionSemantic = project.ext.majorVersion + '.' + project.ext.minorVersion + '+build.' + project.ext.buildVersion
|
||||
|
||||
// Fix for UTF-8 files showing with wrong encoding when compiled on Windows machines.
|
||||
compileJava { options.encoding = "UTF-8" }
|
||||
|
@ -49,7 +51,7 @@ allprojects {
|
|||
javadoc { options.encoding = 'UTF-8' }
|
||||
}
|
||||
|
||||
logger.lifecycle("Building artifact for version $fullVersion")
|
||||
logger.lifecycle("Building artifact for version $fullVersion / $fullVersionFilename / $fullVersionSemantic")
|
||||
|
||||
subprojects {
|
||||
// Build plugins
|
||||
|
@ -67,7 +69,7 @@ subprojects {
|
|||
}
|
||||
|
||||
ext {
|
||||
daggerVersion = "2.47"
|
||||
daggerVersion = "2.51"
|
||||
|
||||
palVersion = "5.1.0"
|
||||
|
||||
|
@ -81,31 +83,32 @@ subprojects {
|
|||
redisBungeeVersion = "0.3.8-SNAPSHOT"
|
||||
redisBungeeProxioDevVersion = "0.7.3"
|
||||
|
||||
commonsTextVersion = "1.10.0"
|
||||
commonsCompressVersion = "1.24.0"
|
||||
commonsCodecVersion = "1.16.0"
|
||||
commonsTextVersion = "1.11.0"
|
||||
commonsCompressVersion = "1.26.0"
|
||||
commonsCodecVersion = "1.16.1"
|
||||
caffeineVersion = "3.1.8"
|
||||
jettyVersion = "11.0.16"
|
||||
jettyVersion = "11.0.20"
|
||||
caffeineVersion = "2.9.2"
|
||||
mysqlVersion = "8.1.0"
|
||||
mariadbVersion = "3.2.0"
|
||||
mysqlVersion = "8.3.0"
|
||||
mariadbVersion = "3.3.3"
|
||||
sqliteVersion = "3.42.0.1"
|
||||
adventureVersion = "4.14.0"
|
||||
hikariVersion = "5.0.1"
|
||||
slf4jVersion = "2.0.9"
|
||||
geoIpVersion = "4.1.0"
|
||||
hikariVersion = "5.1.0"
|
||||
slf4jVersion = "2.0.11"
|
||||
geoIpVersion = "4.2.0"
|
||||
gsonVersion = "2.10.1"
|
||||
dependencyDownloadVersion = "1.3.1"
|
||||
ipAddressMatcherVersion = "5.4.0"
|
||||
ipAddressMatcherVersion = "5.5.0"
|
||||
jasyptVersion = "1.9.3"
|
||||
|
||||
bstatsVersion = "3.0.2"
|
||||
placeholderapiVersion = "2.11.3"
|
||||
placeholderapiVersion = "2.11.5"
|
||||
nkPlaceholderapiVersion = "1.4-SNAPSHOT"
|
||||
|
||||
junitVersion = "5.10.0"
|
||||
mockitoVersion = "5.5.0"
|
||||
testContainersVersion = "1.19.0"
|
||||
swaggerVersion = "2.2.16"
|
||||
junitVersion = "5.10.2"
|
||||
mockitoVersion = "5.11.0"
|
||||
testContainersVersion = "1.19.7"
|
||||
swaggerVersion = "2.2.20"
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
@ -127,6 +130,8 @@ subprojects {
|
|||
testImplementation "com.google.dagger:dagger:$daggerVersion"
|
||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
|
||||
compileOnly "io.swagger.core.v3:swagger-core-jakarta:$swaggerVersion"
|
||||
|
||||
// Test Tooling Dependencies
|
||||
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion" // JUnit 5
|
||||
testImplementation "org.mockito:mockito-core:$mockitoVersion" // Mockito Core
|
||||
|
|
|
@ -16,13 +16,16 @@
|
|||
*/
|
||||
package com.djrapitops.plan.gathering;
|
||||
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -129,4 +132,12 @@ public class BukkitSensor implements ServerSensor<World> {
|
|||
.map(Player::getName)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PluginMetadata> getInstalledPlugins() {
|
||||
return Arrays.stream(Bukkit.getPluginManager().getPlugins())
|
||||
.map(Plugin::getDescription)
|
||||
.map(description -> new PluginMetadata(description.getName(), description.getVersion()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
/**
|
||||
* UserImportRefiner attempts to find any crucial information that is missing.
|
||||
*
|
||||
* <p>
|
||||
* - Finds UUIDs if only name is present.
|
||||
* - Finds Names if only UUID is present.
|
||||
* - Removes any importers that do not have any identifiers.
|
||||
|
|
|
@ -91,7 +91,9 @@ public class PlayerOnlineListener implements Listener {
|
|||
|
||||
String address = event.getHostname();
|
||||
if (!address.isEmpty()) {
|
||||
address = address.substring(0, address.lastIndexOf(':'));
|
||||
if (address.contains(":")) {
|
||||
address = address.substring(0, address.lastIndexOf(':'));
|
||||
}
|
||||
if (address.contains("\u0000")) {
|
||||
address = address.substring(0, address.indexOf('\u0000'));
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ import java.util.logging.Logger;
|
|||
* Task that handles player ping calculation on Bukkit based servers.
|
||||
* <p>
|
||||
* Modified PingManager from LagMonitor plugin.
|
||||
* https://github.com/games647/LagMonitor/blob/master/src/main/java/com/github/games647/lagmonitor/task/PingManager.java
|
||||
* <a href="https://github.com/games647/LagMonitor/blob/master/src/main/java/com/github/games647/lagmonitor/task/PingManager.java">original</a>
|
||||
*
|
||||
* @author games647
|
||||
*/
|
||||
|
@ -89,9 +89,9 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
|
|||
startRecording = new ConcurrentHashMap<>();
|
||||
playerHistory = new HashMap<>();
|
||||
|
||||
Optional<PingMethod> pingMethod = loadPingMethod();
|
||||
if (pingMethod.isPresent()) {
|
||||
this.pingMethod = pingMethod.get();
|
||||
Optional<PingMethod> loaded = loadPingMethod();
|
||||
if (loaded.isPresent()) {
|
||||
this.pingMethod = loaded.get();
|
||||
pingMethodAvailable = true;
|
||||
} else {
|
||||
pingMethodAvailable = false;
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.djrapitops.plan.extension.ExtensionServerDataUpdater;
|
|||
import com.djrapitops.plan.gathering.ShutdownDataPreservation;
|
||||
import com.djrapitops.plan.gathering.ShutdownHook;
|
||||
import com.djrapitops.plan.gathering.timed.BukkitPingCounter;
|
||||
import com.djrapitops.plan.gathering.timed.InstalledPluginGatheringTask;
|
||||
import com.djrapitops.plan.gathering.timed.ServerTPSCounter;
|
||||
import com.djrapitops.plan.gathering.timed.SystemUsageBuffer;
|
||||
import com.djrapitops.plan.settings.upkeep.ConfigStoreTask;
|
||||
|
@ -108,4 +109,8 @@ public interface BukkitTaskModule {
|
|||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindAddressAllowListUpdateTask(AddressAllowList addressAllowList);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindInstalledPluginGatheringTask(InstalledPluginGatheringTask installedPluginGatheringTask);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import java.lang.reflect.Field;
|
|||
* An utility class that simplifies reflection in Bukkit plugins.
|
||||
* <p>
|
||||
* Modified Reflection utility from LagMonitor plugin.
|
||||
* https://github.com/games647/LagMonitor/blob/master/src/main/java/com/github/games647/lagmonitor/traffic/Reflection.java
|
||||
* <a href="https://github.com/games647/LagMonitor/blob/master/src/main/java/com/github/games647/lagmonitor/traffic/Reflection.java">original code</a>
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
package com.djrapitops.plan.gathering;
|
||||
|
||||
import com.djrapitops.plan.PlanBungee;
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
import com.djrapitops.plan.identification.properties.RedisCheck;
|
||||
import com.djrapitops.plan.identification.properties.RedisPlayersOnlineSupplier;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
@ -35,11 +37,13 @@ public class BungeeSensor implements ServerSensor<Object> {
|
|||
private final IntSupplier onlinePlayerCountSupplier;
|
||||
private final IntSupplier onlinePlayerCountBungee;
|
||||
private final Supplier<Collection<ProxiedPlayer>> getPlayers;
|
||||
private final Supplier<Collection<Plugin>> getPlugins;
|
||||
|
||||
@Inject
|
||||
public BungeeSensor(PlanBungee plugin) {
|
||||
getPlayers = plugin.getProxy()::getPlayers;
|
||||
onlinePlayerCountBungee = plugin.getProxy()::getOnlineCount;
|
||||
getPlugins = plugin.getProxy().getPluginManager()::getPlugins;
|
||||
onlinePlayerCountSupplier = RedisCheck.isClassAvailable() ? new RedisPlayersOnlineSupplier() : onlinePlayerCountBungee;
|
||||
}
|
||||
|
||||
|
@ -63,4 +67,12 @@ public class BungeeSensor implements ServerSensor<Object> {
|
|||
public boolean usingRedisBungee() {
|
||||
return RedisCheck.isClassAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PluginMetadata> getInstalledPlugins() {
|
||||
return getPlugins.get().stream()
|
||||
.map(Plugin::getDescription)
|
||||
.map(description -> new PluginMetadata(description.getName(), description.getVersion()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,5 +40,5 @@ public interface BungeeSuperClassBindingModule {
|
|||
ListenerSystem bindListenerSystem(BungeeListenerSystem listenerSystem);
|
||||
|
||||
@Binds
|
||||
ServerSensor<Object> bindServerSensor(BungeeSensor sensor);
|
||||
ServerSensor<?> bindServerSensor(BungeeSensor sensor);
|
||||
}
|
|
@ -23,6 +23,7 @@ import com.djrapitops.plan.delivery.webserver.cache.JSONFileStorage;
|
|||
import com.djrapitops.plan.delivery.webserver.configuration.AddressAllowList;
|
||||
import com.djrapitops.plan.extension.ExtensionServerDataUpdater;
|
||||
import com.djrapitops.plan.gathering.timed.BungeePingCounter;
|
||||
import com.djrapitops.plan.gathering.timed.InstalledPluginGatheringTask;
|
||||
import com.djrapitops.plan.gathering.timed.ProxyTPSCounter;
|
||||
import com.djrapitops.plan.gathering.timed.SystemUsageBuffer;
|
||||
import com.djrapitops.plan.settings.upkeep.NetworkConfigStoreTask;
|
||||
|
@ -87,4 +88,8 @@ public interface BungeeTaskModule {
|
|||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindAddressAllowListUpdateTask(AddressAllowList addressAllowList);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindInstalledPluginGatheringTask(InstalledPluginGatheringTask installedPluginGatheringTask);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import org.apache.tools.ant.filters.ReplaceTokens
|
|||
|
||||
plugins {
|
||||
id "dev.vankka.dependencydownload.plugin" version "$dependencyDownloadVersion"
|
||||
id "com.github.node-gradle.node" version "7.0.0"
|
||||
id "io.swagger.core.v3.swagger-gradle-plugin" version "2.2.16"
|
||||
id "com.github.node-gradle.node" version "7.0.2"
|
||||
id "io.swagger.core.v3.swagger-gradle-plugin" version "2.2.20"
|
||||
}
|
||||
|
||||
configurations {
|
||||
|
@ -64,6 +64,7 @@ dependencies {
|
|||
mysqlDriver "com.mysql:mysql-connector-j:$mysqlVersion"
|
||||
mariadbDriver "org.mariadb.jdbc:mariadb-java-client:$mariadbVersion"
|
||||
sqliteDriver "org.xerial:sqlite-jdbc:$sqliteVersion"
|
||||
sqliteDriver "org.slf4j:slf4j-nop:1.7.36"
|
||||
ipAddressMatcher "com.github.seancfoley:ipaddress:$ipAddressMatcherVersion"
|
||||
|
||||
shadow "org.apache.commons:commons-text:$commonsTextVersion"
|
||||
|
@ -82,6 +83,8 @@ dependencies {
|
|||
// json-simple has junit (a test dependency) compile scoped
|
||||
exclude group: "junit", module: "junit"
|
||||
}
|
||||
implementation "org.jasypt:jasypt:$jasyptVersion:lite"
|
||||
|
||||
|
||||
// Swagger annotations
|
||||
implementation "jakarta.ws.rs:jakarta.ws.rs-api:3.1.0"
|
||||
|
@ -92,7 +95,7 @@ dependencies {
|
|||
testArtifacts project(":extensions:adventure")
|
||||
testImplementation project(":extensions:adventure")
|
||||
testImplementation "com.google.code.gson:gson:$gsonVersion"
|
||||
testImplementation "org.seleniumhq.selenium:selenium-java:4.12.1"
|
||||
testImplementation "org.seleniumhq.selenium:selenium-java:4.18.1"
|
||||
testImplementation "org.testcontainers:testcontainers:$testContainersVersion"
|
||||
testImplementation "org.testcontainers:junit-jupiter:$testContainersVersion"
|
||||
testImplementation "org.testcontainers:nginx:$testContainersVersion"
|
||||
|
@ -111,13 +114,14 @@ task updateVersion(type: Copy) {
|
|||
|
||||
node {
|
||||
download = true
|
||||
version = "16.14.2"
|
||||
version = "20.9.0"
|
||||
nodeProjectDir = file("$rootDir/react/dashboard")
|
||||
}
|
||||
|
||||
task yarnBundle(type: YarnTask) {
|
||||
inputs.files(fileTree("$rootDir/react/dashboard/src"))
|
||||
inputs.file("$rootDir/react/dashboard/package.json")
|
||||
inputs.file("$rootDir/react/dashboard/vite.config.js")
|
||||
|
||||
outputs.dir("$rootDir/react/dashboard/build")
|
||||
|
||||
|
@ -229,8 +233,12 @@ artifacts {
|
|||
}
|
||||
|
||||
processResources {
|
||||
dependsOn copyYarnBuildResults
|
||||
dependsOn determineAssetModifications
|
||||
// Skips Yarn build on Jitpack since Jitpack doesn't offer gclib version compatible with Node 20
|
||||
// Jitpack build is used mainly for java dependencies.
|
||||
if (!project.hasProperty("isJitpack")) {
|
||||
dependsOn copyYarnBuildResults
|
||||
dependsOn determineAssetModifications
|
||||
}
|
||||
dependsOn generateResourceForMySQLDriver
|
||||
dependsOn generateResourceForSQLiteDriver
|
||||
dependsOn generateResourceForIpAddressMatcher
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan;
|
||||
|
||||
import com.djrapitops.plan.component.ComponentSvc;
|
||||
import com.djrapitops.plan.delivery.web.ResolverSvc;
|
||||
import com.djrapitops.plan.delivery.web.ResourceSvc;
|
||||
import com.djrapitops.plan.extension.ExtensionSvc;
|
||||
import com.djrapitops.plan.query.QuerySvc;
|
||||
import com.djrapitops.plan.settings.ListenerSvc;
|
||||
import com.djrapitops.plan.settings.SchedulerSvc;
|
||||
import com.djrapitops.plan.settings.SettingsSvc;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Breaks up {@link PlanSystem} to be a smaller class.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
public class ApiServices {
|
||||
|
||||
private final ComponentSvc componentService;
|
||||
private final ResolverSvc resolverService;
|
||||
private final ResourceSvc resourceService;
|
||||
private final ExtensionSvc extensionService;
|
||||
private final QuerySvc queryService;
|
||||
private final ListenerSvc listenerService;
|
||||
private final SettingsSvc settingsService;
|
||||
private final SchedulerSvc schedulerService;
|
||||
|
||||
@Inject
|
||||
public ApiServices(
|
||||
ComponentSvc componentService,
|
||||
ResolverSvc resolverService,
|
||||
ResourceSvc resourceService,
|
||||
ExtensionSvc extensionService,
|
||||
QuerySvc queryService,
|
||||
ListenerSvc listenerService,
|
||||
SettingsSvc settingsService,
|
||||
SchedulerSvc schedulerService
|
||||
) {
|
||||
this.componentService = componentService;
|
||||
this.resolverService = resolverService;
|
||||
this.resourceService = resourceService;
|
||||
this.extensionService = extensionService;
|
||||
this.queryService = queryService;
|
||||
this.listenerService = listenerService;
|
||||
this.settingsService = settingsService;
|
||||
this.schedulerService = schedulerService;
|
||||
}
|
||||
|
||||
public void register() {
|
||||
extensionService.register();
|
||||
componentService.register();
|
||||
resolverService.register();
|
||||
resourceService.register();
|
||||
listenerService.register();
|
||||
settingsService.register();
|
||||
schedulerService.register();
|
||||
queryService.register();
|
||||
}
|
||||
|
||||
public void registerExtensions() {
|
||||
extensionService.registerExtensions();
|
||||
}
|
||||
|
||||
public void disableExtensionDataUpdates() {
|
||||
extensionService.disableUpdates();
|
||||
}
|
||||
|
||||
public ComponentSvc getComponentService() {
|
||||
return componentService;
|
||||
}
|
||||
|
||||
public ResolverSvc getResolverService() {
|
||||
return resolverService;
|
||||
}
|
||||
|
||||
public ResourceSvc getResourceService() {
|
||||
return resourceService;
|
||||
}
|
||||
|
||||
public ExtensionSvc getExtensionService() {
|
||||
return extensionService;
|
||||
}
|
||||
|
||||
public QuerySvc getQueryService() {
|
||||
return queryService;
|
||||
}
|
||||
|
||||
public ListenerSvc getListenerService() {
|
||||
return listenerService;
|
||||
}
|
||||
|
||||
public SettingsSvc getSettingsService() {
|
||||
return settingsService;
|
||||
}
|
||||
|
||||
public SchedulerSvc getSchedulerService() {
|
||||
return schedulerService;
|
||||
}
|
||||
}
|
|
@ -17,25 +17,17 @@
|
|||
package com.djrapitops.plan;
|
||||
|
||||
import com.djrapitops.plan.api.PlanAPI;
|
||||
import com.djrapitops.plan.component.ComponentSvc;
|
||||
import com.djrapitops.plan.delivery.DeliveryUtilities;
|
||||
import com.djrapitops.plan.delivery.export.ExportSystem;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.web.ResolverSvc;
|
||||
import com.djrapitops.plan.delivery.web.ResourceSvc;
|
||||
import com.djrapitops.plan.delivery.webserver.NonProxyWebserverDisableChecker;
|
||||
import com.djrapitops.plan.delivery.webserver.WebServerSystem;
|
||||
import com.djrapitops.plan.extension.ExtensionSvc;
|
||||
import com.djrapitops.plan.gathering.cache.CacheSystem;
|
||||
import com.djrapitops.plan.gathering.importing.ImportSystem;
|
||||
import com.djrapitops.plan.gathering.listeners.ListenerSystem;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.processing.Processing;
|
||||
import com.djrapitops.plan.query.QuerySvc;
|
||||
import com.djrapitops.plan.settings.ConfigSystem;
|
||||
import com.djrapitops.plan.settings.ListenerSvc;
|
||||
import com.djrapitops.plan.settings.SchedulerSvc;
|
||||
import com.djrapitops.plan.settings.SettingsSvc;
|
||||
import com.djrapitops.plan.settings.locale.LocaleSystem;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
|
@ -77,14 +69,7 @@ public class PlanSystem implements SubSystem {
|
|||
private final ImportSystem importSystem;
|
||||
private final ExportSystem exportSystem;
|
||||
private final DeliveryUtilities deliveryUtilities;
|
||||
private final ComponentSvc componentService;
|
||||
private final ResolverSvc resolverService;
|
||||
private final ResourceSvc resourceService;
|
||||
private final ExtensionSvc extensionService;
|
||||
private final QuerySvc queryService;
|
||||
private final ListenerSvc listenerService;
|
||||
private final SettingsSvc settingsService;
|
||||
private final SchedulerSvc schedulerService;
|
||||
private final ApiServices apiServices;
|
||||
private final PluginLogger logger;
|
||||
private final ErrorLogger errorLogger;
|
||||
|
||||
|
@ -104,16 +89,9 @@ public class PlanSystem implements SubSystem {
|
|||
ImportSystem importSystem,
|
||||
ExportSystem exportSystem,
|
||||
DeliveryUtilities deliveryUtilities,
|
||||
ComponentSvc componentService,
|
||||
ResolverSvc resolverService,
|
||||
ResourceSvc resourceService,
|
||||
ExtensionSvc extensionService,
|
||||
QuerySvc queryService,
|
||||
ListenerSvc listenerService,
|
||||
SettingsSvc settingsService,
|
||||
SchedulerSvc schedulerService,
|
||||
PluginLogger logger,
|
||||
ErrorLogger errorLogger,
|
||||
ApiServices apiServices, // API v5
|
||||
@SuppressWarnings("deprecation") PlanAPI.PlanAPIHolder apiHolder // Deprecated PlanAPI, backwards compatibility
|
||||
) {
|
||||
this.files = files;
|
||||
|
@ -130,16 +108,9 @@ public class PlanSystem implements SubSystem {
|
|||
this.importSystem = importSystem;
|
||||
this.exportSystem = exportSystem;
|
||||
this.deliveryUtilities = deliveryUtilities;
|
||||
this.componentService = componentService;
|
||||
this.resolverService = resolverService;
|
||||
this.resourceService = resourceService;
|
||||
this.extensionService = extensionService;
|
||||
this.queryService = queryService;
|
||||
this.listenerService = listenerService;
|
||||
this.settingsService = settingsService;
|
||||
this.schedulerService = schedulerService;
|
||||
this.logger = logger;
|
||||
this.errorLogger = errorLogger;
|
||||
this.apiServices = apiServices;
|
||||
|
||||
logger.info("§2");
|
||||
logger.info("§2 ██▌");
|
||||
|
@ -162,14 +133,7 @@ public class PlanSystem implements SubSystem {
|
|||
* Enables the rest of the systems that are not enabled in {@link #enableForCommands()}.
|
||||
*/
|
||||
public void enableOtherThanCommands() {
|
||||
extensionService.register();
|
||||
componentService.register();
|
||||
resolverService.register();
|
||||
resourceService.register();
|
||||
listenerService.register();
|
||||
settingsService.register();
|
||||
schedulerService.register();
|
||||
queryService.register();
|
||||
apiServices.register();
|
||||
|
||||
enableSystems(
|
||||
processing,
|
||||
|
@ -189,11 +153,11 @@ public class PlanSystem implements SubSystem {
|
|||
// Disables Webserver if Proxy is detected in the database
|
||||
if (serverInfo.getServer().isNotProxy()) {
|
||||
processing.submitNonCritical(new NonProxyWebserverDisableChecker(
|
||||
configSystem.getConfig(), webServerSystem.getAddresses(), webServerSystem, logger, errorLogger
|
||||
configSystem.getConfig(), localeSystem.getLocale(), webServerSystem.getAddresses(), webServerSystem, logger, errorLogger
|
||||
));
|
||||
}
|
||||
|
||||
extensionService.registerExtensions();
|
||||
apiServices.registerExtensions();
|
||||
enabled = true;
|
||||
|
||||
String javaVersion = System.getProperty("java.specification.version");
|
||||
|
@ -223,7 +187,7 @@ public class PlanSystem implements SubSystem {
|
|||
enabled = false;
|
||||
Formatters.clearSingleton();
|
||||
|
||||
extensionService.disableUpdates();
|
||||
apiServices.disableExtensionDataUpdates();
|
||||
|
||||
disableSystems(
|
||||
taskSystem,
|
||||
|
@ -316,12 +280,8 @@ public class PlanSystem implements SubSystem {
|
|||
return enabled;
|
||||
}
|
||||
|
||||
public ExtensionSvc getExtensionService() {
|
||||
return extensionService;
|
||||
}
|
||||
|
||||
public ComponentSvc getComponentService() {
|
||||
return componentService;
|
||||
public ApiServices getApiServices() {
|
||||
return apiServices;
|
||||
}
|
||||
|
||||
public static long getServerEnableTime() {
|
||||
|
|
|
@ -43,7 +43,7 @@ import java.util.stream.Collectors;
|
|||
* PlanAPI extension for all implementations.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5).
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (<a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5">wiki</a>).
|
||||
*/
|
||||
@Singleton
|
||||
@Deprecated(forRemoval = true, since = "5.0")
|
||||
|
|
|
@ -35,7 +35,7 @@ import java.util.UUID;
|
|||
* Interface for PlanAPI methods.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5).
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (<a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5">wiki</a>).
|
||||
*/
|
||||
@Deprecated(since = "5.0")
|
||||
public interface PlanAPI {
|
||||
|
@ -65,7 +65,7 @@ public interface PlanAPI {
|
|||
}
|
||||
|
||||
/**
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
* @deprecated PluginData API has been deprecated - see <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API">wiki</a> for new API.
|
||||
*/
|
||||
@Deprecated
|
||||
void addPluginDataSource(PluginData pluginData);
|
||||
|
|
|
@ -29,7 +29,7 @@ import java.util.Optional;
|
|||
* The Keys might change in the future, but the Optional API should help dealing with those cases.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5).
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (<a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5">wiki</a>).
|
||||
*/
|
||||
@Deprecated(since = "5.0")
|
||||
public class PlayerContainer {
|
||||
|
|
|
@ -28,7 +28,7 @@ import java.util.Optional;
|
|||
* The Keys might change in the future, but the Optional API should help dealing with those cases.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5).
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (<a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5">wiki</a>).
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "5.0")
|
||||
public class ServerContainer {
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.djrapitops.plan.delivery.formatting.Formatter;
|
|||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.exceptions.ExportException;
|
||||
import com.djrapitops.plan.gathering.domain.GeoInfo;
|
||||
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
|
||||
import com.djrapitops.plan.gathering.importing.ImportSystem;
|
||||
import com.djrapitops.plan.gathering.importing.importers.Importer;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
|
@ -42,6 +43,7 @@ import com.djrapitops.plan.settings.locale.Locale;
|
|||
import com.djrapitops.plan.settings.locale.lang.CommandLang;
|
||||
import com.djrapitops.plan.settings.locale.lang.GenericLang;
|
||||
import com.djrapitops.plan.settings.locale.lang.HelpLang;
|
||||
import com.djrapitops.plan.settings.locale.lang.HtmlLang;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries;
|
||||
|
@ -258,12 +260,17 @@ public class DataUtilityCommands {
|
|||
Optional<GeoInfo> mostRecentGeoInfo = new GeoInfoMutator(geoInfo).mostRecent();
|
||||
String geolocation = mostRecentGeoInfo.isPresent() ? mostRecentGeoInfo.get().getGeolocation() : "-";
|
||||
SessionsMutator sessionsMutator = SessionsMutator.forContainer(player);
|
||||
String latestJoinAddress = sessionsMutator.latestSession()
|
||||
.flatMap(session -> session.getExtraData(JoinAddress.class))
|
||||
.map(JoinAddress::getAddress)
|
||||
.orElse("-");
|
||||
|
||||
String table = locale.getString(CommandLang.HEADER_INSPECT, playerName) + '\n' +
|
||||
locale.getString(CommandLang.INGAME_ACTIVITY_INDEX, activityIndex.getFormattedValue(formatters.decimals()), activityIndex.getGroup()) + '\n' +
|
||||
locale.getString(CommandLang.INGAME_REGISTERED, timestamp.apply(() -> registered)) + '\n' +
|
||||
locale.getString(CommandLang.INGAME_LAST_SEEN, timestamp.apply(() -> lastSeen)) + '\n' +
|
||||
locale.getString(CommandLang.INGAME_GEOLOCATION, geolocation) + '\n' +
|
||||
locale.getString(HtmlLang.TITLE_LATEST_JOIN_ADDRESSES, latestJoinAddress) + '\n' +
|
||||
locale.getString(CommandLang.INGAME_TIMES_KICKED, player.getValue(PlayerKeys.KICK_COUNT).orElse(0)) + '\n' +
|
||||
'\n' +
|
||||
locale.getString(CommandLang.INGAME_PLAYTIME, length.apply(sessionsMutator.toPlaytime())) + '\n' +
|
||||
|
|
|
@ -27,7 +27,7 @@ import java.util.UUID;
|
|||
* @author AuroraLS3
|
||||
* @see TableContainer
|
||||
* @see InspectContainer
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
* @deprecated PluginData API has been deprecated - see <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API">wiki</a> for new API.
|
||||
*/
|
||||
@Deprecated(since = "5.0")
|
||||
public final class AnalysisContainer extends InspectContainer {
|
||||
|
|
|
@ -27,7 +27,7 @@ import java.util.TreeMap;
|
|||
*
|
||||
* @author AuroraLS3
|
||||
* @see TableContainer
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
* @deprecated PluginData API has been deprecated - see <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API">wiki</a> for new API.
|
||||
*/
|
||||
@Deprecated(since = "5.0")
|
||||
public class InspectContainer {
|
||||
|
|
|
@ -26,7 +26,7 @@ import java.util.List;
|
|||
* Container used for creating Html tables.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
* @deprecated PluginData API has been deprecated - see <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API">wiki</a> for new API.
|
||||
*/
|
||||
@Deprecated(since = "5.0")
|
||||
public class TableContainer {
|
||||
|
|
|
@ -23,7 +23,7 @@ import java.util.UUID;
|
|||
* Interface for PluginData objects that affect Ban state of players.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
* @deprecated PluginData API has been deprecated - see <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API">wiki</a> for new API.
|
||||
*/
|
||||
@Deprecated(since = "5.0")
|
||||
public interface BanData {
|
||||
|
|
|
@ -20,7 +20,7 @@ package com.djrapitops.plan.data.plugin;
|
|||
* Enum class for PluginData to estimate the required width of the contained items.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
* @deprecated PluginData API has been deprecated - see <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API">wiki</a> for new API.
|
||||
*/
|
||||
@Deprecated
|
||||
public enum ContainerSize {
|
||||
|
|
|
@ -33,7 +33,7 @@ import java.util.UUID;
|
|||
* to register objects extending this class.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
* @deprecated PluginData API has been deprecated - see <a href="https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API">wiki</a> for new API.
|
||||
*/
|
||||
@Deprecated(since = "5.0")
|
||||
public abstract class PluginData {
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents plugin version history.
|
||||
* <p>
|
||||
* If version is null the plugin was uninstalled at that time.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class PluginHistoryMetadata {
|
||||
|
||||
private final String name;
|
||||
@Nullable
|
||||
private final String version;
|
||||
private final long modified;
|
||||
|
||||
public PluginHistoryMetadata(String name, @Nullable String version, long modified) {
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
this.modified = modified;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public long getModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PluginHistoryMetadata that = (PluginHistoryMetadata) o;
|
||||
return getModified() == that.getModified() && Objects.equals(getName(), that.getName()) && Objects.equals(getVersion(), that.getVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getName(), getVersion(), getModified());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PluginHistoryMetadata{" +
|
||||
"name='" + name + '\'' +
|
||||
", version='" + version + '\'' +
|
||||
", modified=" + modified +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery.domain;
|
|||
|
||||
import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex;
|
||||
import com.djrapitops.plan.gathering.domain.BaseUser;
|
||||
import com.djrapitops.plan.gathering.domain.Ping;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
@ -38,6 +39,7 @@ public class TablePlayer implements Comparable<TablePlayer> {
|
|||
private Long registered;
|
||||
private Long lastSeen;
|
||||
private String geolocation;
|
||||
private Ping ping;
|
||||
|
||||
private boolean banned = false;
|
||||
|
||||
|
@ -87,6 +89,10 @@ public class TablePlayer implements Comparable<TablePlayer> {
|
|||
return Optional.ofNullable(geolocation);
|
||||
}
|
||||
|
||||
public Ping getPing() {
|
||||
return ping;
|
||||
}
|
||||
|
||||
public boolean isBanned() {
|
||||
return banned;
|
||||
}
|
||||
|
@ -111,12 +117,13 @@ public class TablePlayer implements Comparable<TablePlayer> {
|
|||
lastSeen.equals(that.lastSeen) &&
|
||||
name.equals(that.name) &&
|
||||
activityIndex.equals(that.activityIndex) &&
|
||||
geolocation.equals(that.geolocation);
|
||||
geolocation.equals(that.geolocation) &&
|
||||
ping.equals(that.ping);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, activityIndex, activePlaytime, sessionCount, registered, lastSeen, geolocation);
|
||||
return Objects.hash(name, activityIndex, activePlaytime, sessionCount, registered, lastSeen, geolocation, ping);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -130,6 +137,7 @@ public class TablePlayer implements Comparable<TablePlayer> {
|
|||
", registered=" + registered +
|
||||
", lastSeen=" + lastSeen +
|
||||
", geolocation='" + geolocation + '\'' +
|
||||
", ping='" + ping + '\'' +
|
||||
", banned=" + banned +
|
||||
'}';
|
||||
}
|
||||
|
@ -190,6 +198,11 @@ public class TablePlayer implements Comparable<TablePlayer> {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder ping(Ping ping) {
|
||||
player.ping = ping;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TablePlayer build() {
|
||||
return player;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ public enum WebPermission implements Supplier<String>, Lang {
|
|||
PAGE_NETWORK_OVERVIEW_GRAPHS_ONLINE("See Players Online graph"),
|
||||
PAGE_NETWORK_OVERVIEW_GRAPHS_DAY_BY_DAY("See Day by Day graph"),
|
||||
PAGE_NETWORK_OVERVIEW_GRAPHS_HOUR_BY_HOUR("See Hour by Hour graph"),
|
||||
PAGE_NETWORK_OVERVIEW_GRAPHS_CALENDAR("See Network calendar"),
|
||||
PAGE_NETWORK_SERVER_LIST("See list of servers"),
|
||||
PAGE_NETWORK_PLAYERBASE("See Playerbase Overview -tab"),
|
||||
PAGE_NETWORK_PLAYERBASE_OVERVIEW("See Playerbase Overview numbers"),
|
||||
|
@ -54,6 +55,7 @@ public enum WebPermission implements Supplier<String>, Lang {
|
|||
PAGE_NETWORK_GEOLOCATIONS_PING_PER_COUNTRY("See Ping Per Country table"),
|
||||
PAGE_NETWORK_PLAYERS("See Player list -tab"),
|
||||
PAGE_NETWORK_PERFORMANCE("See network Performance tab"),
|
||||
PAGE_NETWORK_PLUGIN_HISTORY("See Plugin History across the network"),
|
||||
PAGE_NETWORK_PLUGINS("See Plugins tab of Proxy"),
|
||||
|
||||
PAGE_SERVER("See all of server page"),
|
||||
|
@ -89,6 +91,7 @@ public enum WebPermission implements Supplier<String>, Lang {
|
|||
PAGE_SERVER_PERFORMANCE("See Performance tab"),
|
||||
PAGE_SERVER_PERFORMANCE_GRAPHS("See Performance graphs"),
|
||||
PAGE_SERVER_PERFORMANCE_OVERVIEW("See Performance numbers"),
|
||||
PAGE_SERVER_PLUGIN_HISTORY("See Plugin History"),
|
||||
PAGE_SERVER_PLUGINS("See Plugins -tabs of servers"),
|
||||
|
||||
PAGE_PLAYER("See all of player page"),
|
||||
|
|
|
@ -139,7 +139,6 @@ public interface DataContainer {
|
|||
|
||||
/**
|
||||
* Get formatted Value identified by the Key.
|
||||
* <p>
|
||||
*
|
||||
* @param key Key that identifies the Value
|
||||
* @param formatter Formatter for the Optional returned by {@link DataContainer#getValue(Key)}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 com.djrapitops.plan.delivery.domain.PluginHistoryMetadata;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* History of plugin versions, sorted most recent first.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class PluginHistoryDto {
|
||||
|
||||
private final List<PluginHistoryMetadata> history;
|
||||
|
||||
public PluginHistoryDto(List<PluginHistoryMetadata> history) {
|
||||
this.history = history;
|
||||
}
|
||||
|
||||
public List<PluginHistoryMetadata> getHistory() {
|
||||
return history;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PluginHistoryDto that = (PluginHistoryDto) o;
|
||||
return Objects.equals(getHistory(), that.getHistory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getHistory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PluginHistoryDto{" +
|
||||
"history=" + history +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package com.djrapitops.plan.delivery.domain.datatransfer;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionValueDataDto;
|
||||
import com.djrapitops.plan.gathering.domain.Ping;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
@ -36,6 +37,9 @@ public class TablePlayerDto {
|
|||
private Long lastSeen;
|
||||
private Long registered;
|
||||
private String country;
|
||||
private Double pingAverage;
|
||||
private Integer pingMax;
|
||||
private Integer pingMin;
|
||||
|
||||
private Map<String, ExtensionValueDataDto> extensionValues;
|
||||
|
||||
|
@ -119,6 +123,30 @@ public class TablePlayerDto {
|
|||
this.playerUUID = playerUUID;
|
||||
}
|
||||
|
||||
public Double getPingAverage() {
|
||||
return pingAverage;
|
||||
}
|
||||
|
||||
public void setPingAverage(Double pingAverage) {
|
||||
this.pingAverage = pingAverage;
|
||||
}
|
||||
|
||||
public Integer getPingMax() {
|
||||
return pingMax;
|
||||
}
|
||||
|
||||
public void setPingMax(Integer pingMax) {
|
||||
this.pingMax = pingMax;
|
||||
}
|
||||
|
||||
public Integer getPingMin() {
|
||||
return pingMin;
|
||||
}
|
||||
|
||||
public void setPingMin(Integer pingMin) {
|
||||
this.pingMin = pingMin;
|
||||
}
|
||||
|
||||
public static final class TablePlayerDtoBuilder {
|
||||
private final TablePlayerDto tablePlayerDto;
|
||||
|
||||
|
@ -169,6 +197,15 @@ public class TablePlayerDto {
|
|||
return this;
|
||||
}
|
||||
|
||||
public TablePlayerDtoBuilder withPing(Ping ping) {
|
||||
if (ping != null) {
|
||||
tablePlayerDto.setPingAverage(ping.getAverage());
|
||||
tablePlayerDto.setPingMax(ping.getMax());
|
||||
tablePlayerDto.setPingMin(ping.getMin());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public TablePlayerDto build() {return tablePlayerDto;}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.djrapitops.plan.delivery.formatting.Formatters;
|
|||
import com.djrapitops.plan.delivery.rendering.html.Html;
|
||||
import com.djrapitops.plan.extension.table.Table;
|
||||
import com.djrapitops.plan.extension.table.TableColumnFormat;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -76,7 +77,7 @@ public class TableDto {
|
|||
case DATE_SECOND:
|
||||
return Formatters.getInstance().secondLong().apply(Long.parseLong(value.toString()));
|
||||
case PLAYER_NAME:
|
||||
return Html.LINK.create("../player/" + Html.encodeToURL(value.toString()));
|
||||
return Html.LINK.create("../player/" + Html.encodeToURL(value.toString()), StringEscapeUtils.escapeHtml4(value.toString()));
|
||||
default:
|
||||
return value.toString();
|
||||
}
|
||||
|
|
|
@ -28,15 +28,15 @@ import java.util.Objects;
|
|||
*/
|
||||
public class ServerSpecificLineGraph {
|
||||
|
||||
private final List<Double[]> points;
|
||||
private final List<Number[]> points;
|
||||
private final ServerDto server;
|
||||
|
||||
public ServerSpecificLineGraph(List<Double[]> points, ServerDto server) {
|
||||
public ServerSpecificLineGraph(List<Number[]> points, ServerDto server) {
|
||||
this.points = points;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public List<Double[]> getPoints() {
|
||||
public List<Number[]> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,15 +38,16 @@ import java.util.concurrent.TimeUnit;
|
|||
* <p>
|
||||
* Activity for a single week is calculated using {@code A(t) = (1 / (pi/2 * (t/T) + 1))}.
|
||||
* A(t) is based on function f(x) = 1 / (x + 1), which has property f(0) = 1, decreasing from there, but not in a straight line.
|
||||
* You can see the function plotted here https://www.wolframalpha.com/input/?i=1+%2F+(x%2B1)+from+-1+to+2
|
||||
* You can see the function plotted <a href="https://www.wolframalpha.com/input/?i=1+%2F+(x%2B1)+from+-1+to+2">here</a>
|
||||
* <p>
|
||||
* To fine tune the curve pi/2 is used since it felt like a good curve.
|
||||
* <p>
|
||||
* Activity index A is calculated by using the formula:
|
||||
* {@code A = 5 - 5 * [A(t1) + A(t2) + A(t3)] / 3}
|
||||
* <p>
|
||||
* <a href="https://www.wolframalpha.com/input/?i=plot+y+%3D+5+-+5+*+(1+%2F+(pi%2F2+*+x%2B1))+and+y+%3D1+and+y+%3D+2+and+y+%3D+3+and+y+%3D+3.75+from+-0.5+to+3">
|
||||
* Plot for A and limits
|
||||
* https://www.wolframalpha.com/input/?i=plot+y+%3D+5+-+5+*+(1+%2F+(pi%2F2+*+x%2B1))+and+y+%3D1+and+y+%3D+2+and+y+%3D+3+and+y+%3D+3.75+from+-0.5+to+3
|
||||
* </a>
|
||||
* <p>
|
||||
* New Limits for A would thus be
|
||||
* {@code < 1: Inactive}
|
||||
|
@ -90,6 +91,16 @@ public class ActivityIndex {
|
|||
return getGroups(null);
|
||||
}
|
||||
|
||||
public static String[] getDefaultGroupLangKeys() {
|
||||
return new String[]{
|
||||
HtmlLang.INDEX_VERY_ACTIVE.getKey(),
|
||||
HtmlLang.INDEX_ACTIVE.getKey(),
|
||||
HtmlLang.INDEX_REGULAR.getKey(),
|
||||
HtmlLang.INDEX_IRREGULAR.getKey(),
|
||||
HtmlLang.INDEX_INACTIVE.getKey()
|
||||
};
|
||||
}
|
||||
|
||||
public static String[] getGroups(Locale locale) {
|
||||
if (locale == null) {
|
||||
return new String[]{
|
||||
|
|
|
@ -166,6 +166,12 @@ public class Exporter extends FileExporter {
|
|||
}
|
||||
|
||||
public void exportReact() throws ExportException {
|
||||
if (config.isFalse(ExportSettings.PLAYER_PAGES)
|
||||
&& config.isFalse(ExportSettings.SERVER_PAGE)
|
||||
&& config.isFalse(ExportSettings.PLAYERS_PAGE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path toDirectory = config.getPageExportPath();
|
||||
|
||||
try {
|
||||
|
|
|
@ -126,6 +126,7 @@ public class NetworkPageExporter extends FileExporter {
|
|||
"graph?type=activity",
|
||||
"graph?type=geolocation",
|
||||
"graph?type=uniqueAndNew",
|
||||
"graph?type=serverCalendar",
|
||||
"network/pingTable",
|
||||
"sessions",
|
||||
"extensionData?server=" + serverUUID,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package com.djrapitops.plan.delivery.export;
|
||||
|
||||
import com.djrapitops.plan.delivery.rendering.BundleAddressCorrection;
|
||||
import com.djrapitops.plan.delivery.web.AssetVersions;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
|
@ -52,23 +53,25 @@ public class ReactExporter extends FileExporter {
|
|||
private final PlanConfig config;
|
||||
private final RootJSONResolver jsonHandler;
|
||||
private final AssetVersions assetVersions;
|
||||
private final BundleAddressCorrection bundleAddressCorrection;
|
||||
|
||||
@Inject
|
||||
public ReactExporter(
|
||||
PlanFiles files,
|
||||
PlanConfig config,
|
||||
RootJSONResolver jsonHandler,
|
||||
AssetVersions assetVersions
|
||||
AssetVersions assetVersions,
|
||||
BundleAddressCorrection bundleAddressCorrection
|
||||
) {
|
||||
this.files = files;
|
||||
this.config = config;
|
||||
this.jsonHandler = jsonHandler;
|
||||
this.assetVersions = assetVersions;
|
||||
this.bundleAddressCorrection = bundleAddressCorrection;
|
||||
}
|
||||
|
||||
public void exportReactFiles(Path toDirectory) throws IOException {
|
||||
exportIndexHtml(toDirectory);
|
||||
exportAsset(toDirectory, "asset-manifest.json");
|
||||
exportAsset(toDirectory, "favicon.ico");
|
||||
exportAsset(toDirectory, "logo192.png");
|
||||
exportAsset(toDirectory, "logo512.png");
|
||||
|
@ -104,17 +107,18 @@ public class ReactExporter extends FileExporter {
|
|||
Path to = toDirectory.resolve(path);
|
||||
Resource resource = files.getResourceFromJar("web/" + path);
|
||||
// Make static asset loading work with subdirectory addresses
|
||||
if (path.endsWith(".css") || "asset-manifest.json".equals(path)) {
|
||||
if (path.endsWith(".css")) {
|
||||
String contents = resource.asString();
|
||||
String withReplacedStatic = StringUtils.replace(contents, "/static", getBasePath() + "/static");
|
||||
export(to, withReplacedStatic);
|
||||
String withReplaced = bundleAddressCorrection.correctAddressForExport(contents, path);
|
||||
export(to, withReplaced);
|
||||
} else if (path.endsWith(".js")) {
|
||||
String withReplacedConstants = StringUtils.replaceEach(
|
||||
resource.asString(),
|
||||
new String[]{"PLAN_BASE_ADDRESS", "PLAN_EXPORTED_VERSION", ".p=\"/\""},
|
||||
new String[]{config.get(WebserverSettings.EXTERNAL_LINK), "true", ".p=\"" + getBasePath() + "/\""}
|
||||
new String[]{"PLAN_BASE_ADDRESS", "PLAN_EXPORTED_VERSION"},
|
||||
new String[]{config.get(WebserverSettings.EXTERNAL_LINK), "true"}
|
||||
);
|
||||
export(to, withReplacedConstants);
|
||||
String withReplaced = bundleAddressCorrection.correctAddressForExport(withReplacedConstants, path);
|
||||
export(to, withReplaced);
|
||||
} else {
|
||||
export(to, resource);
|
||||
}
|
||||
|
@ -141,25 +145,11 @@ public class ReactExporter extends FileExporter {
|
|||
private void exportIndexHtml(Path toDirectory) throws IOException {
|
||||
String contents = files.getResourceFromJar("web/index.html")
|
||||
.asString();
|
||||
String basePath = getBasePath();
|
||||
contents = StringUtils.replaceEach(contents,
|
||||
new String[]{"/static", "/pageExtensionApi.js"},
|
||||
new String[]{basePath + "/static", basePath + "/pageExtensionApi.js"});
|
||||
contents = bundleAddressCorrection.correctAddressForExport(contents, "index.html");
|
||||
|
||||
export(toDirectory.resolve("index.html"), contents);
|
||||
}
|
||||
|
||||
private String getBasePath() {
|
||||
String basePath = config.get(WebserverSettings.EXTERNAL_LINK)
|
||||
.replace("http://", "")
|
||||
.replace("https://", "");
|
||||
if (StringUtils.contains(basePath, '/')) {
|
||||
return basePath.substring(StringUtils.indexOf(basePath, '/'));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private void exportAsset(Path toDirectory, String asset) throws IOException {
|
||||
export(toDirectory.resolve(asset), files.getResourceFromJar("web/" + asset));
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.TimeZone;
|
|||
|
||||
/**
|
||||
* Formats timestamps to the Last-Modified header time format.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified">Documentation for the header</a>
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import com.djrapitops.plan.delivery.webserver.Addresses;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* In charge of correcting the root address in the javascript bundle.
|
||||
* <p>
|
||||
* The javascript bundle assumes everything is hosted at /,
|
||||
* but hosting settings affect the address and it could be hosted at a subdirectory like /plan/
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
public class BundleAddressCorrection {
|
||||
|
||||
private static final String STATIC = "static";
|
||||
private static final Pattern JAVASCRIPT_ADDRESS_PATTERN = Pattern.compile("\"(\\./|/?static)(.+?)\\.(json|js|css)\"");
|
||||
|
||||
private final PlanConfig config;
|
||||
private final Addresses addresses;
|
||||
|
||||
@Inject
|
||||
public BundleAddressCorrection(PlanConfig config, Addresses addresses) {
|
||||
this.config = config;
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
private String getExportBasePath() {
|
||||
return addresses.getBasePath(config.get(WebserverSettings.EXTERNAL_LINK));
|
||||
}
|
||||
|
||||
private String getWebserverBasePath() {
|
||||
String address = addresses.getMainAddress()
|
||||
.orElseGet(addresses::getFallbackLocalhostAddress);
|
||||
return addresses.getBasePath(address);
|
||||
}
|
||||
|
||||
public String correctAddressForWebserver(String content, String fileName) {
|
||||
String basePath = getWebserverBasePath();
|
||||
return correctAddress(content, fileName, basePath);
|
||||
}
|
||||
|
||||
public String correctAddressForExport(String content, String fileName) {
|
||||
String basePath = getExportBasePath();
|
||||
return correctAddress(content, fileName, basePath);
|
||||
}
|
||||
|
||||
// basePath is either empty if the address doesn't have a subdirectory, or a subdirectory.
|
||||
@Nullable
|
||||
private String correctAddress(String content, String fileName, String basePath) {
|
||||
if (fileName.endsWith(".css")) {
|
||||
return correctAddressInCss(content, basePath);
|
||||
} else if (fileName.endsWith(".js")) {
|
||||
return correctAddressInJavascript(content, basePath);
|
||||
} else if ("index.html".equals(fileName)) {
|
||||
return correctAddressInHtml(content, basePath);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private String correctAddressInHtml(String content, String basePath) {
|
||||
String endingSlash = basePath.endsWith("/") ? "" : "/";
|
||||
return StringUtils.replaceEach(content,
|
||||
new String[]{"src=\"/", "href=\"/"},
|
||||
new String[]{"src=\"" + basePath + endingSlash, "href=\"" + basePath + endingSlash});
|
||||
}
|
||||
|
||||
private String correctAddressInCss(String content, String basePath) {
|
||||
String endingSlash = basePath.endsWith("/") ? "" : "/";
|
||||
return StringUtils.replace(content, "/static", basePath + endingSlash + STATIC);
|
||||
}
|
||||
|
||||
private String correctAddressInJavascript(String content, String basePath) {
|
||||
int lastIndex = 0;
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
Matcher matcher = JAVASCRIPT_ADDRESS_PATTERN.matcher(content);
|
||||
while (matcher.find()) {
|
||||
String addressStart = matcher.group(1);
|
||||
String file = matcher.group(2);
|
||||
String extension = matcher.group(3);
|
||||
int startIndex = matcher.start();
|
||||
int endIndex = matcher.end();
|
||||
|
||||
// If basePath is empty the website is hosted at root of the tree /
|
||||
boolean atUrlRoot = basePath.isEmpty();
|
||||
|
||||
// This handles /static and static representation
|
||||
boolean startsWithSlash = addressStart.startsWith("/");
|
||||
String startingSlash = startsWithSlash ? "/" : "";
|
||||
// This handles basePath containing a slash after subdirectory, such as /plan/ instead of /plan
|
||||
String endingSlash = basePath.endsWith("/") ? "" : "/";
|
||||
|
||||
// Without subdirectory we can use the address as is, and it doesn't need changes,
|
||||
// otherwise we can add the directory to the start.
|
||||
String staticReplacement = atUrlRoot
|
||||
? startingSlash + STATIC
|
||||
: basePath + endingSlash + STATIC;
|
||||
String relativeReplacement = atUrlRoot
|
||||
? "./"
|
||||
: basePath + endingSlash + "static/";
|
||||
|
||||
// Replaces basePath starting slash if the replaced thing does not start with slash
|
||||
if (!startsWithSlash && staticReplacement.startsWith("/")) {
|
||||
staticReplacement = staticReplacement.substring(1);
|
||||
}
|
||||
|
||||
// Replacement examples when basepath is empty, "/plan" or "/plan/"
|
||||
// "./Filename-hash.js" -> "./Filename-hash.js" or "/plan/static/Filename-hash.js"
|
||||
// "/static/Filename-hash.js" -> "/static/Filename-hash.js" or "/plan/static/Filename-hash.js"
|
||||
// "static/Filename-hash.js" -> "static/Filename-hash.js" or "plan/static/Filename-hash.js"
|
||||
String replacementAddress = StringUtils.equalsAny(addressStart, "/static", STATIC)
|
||||
? staticReplacement
|
||||
: relativeReplacement;
|
||||
String replacement = '"' + replacementAddress + file + '.' + extension + '"';
|
||||
|
||||
output.append(content, lastIndex, startIndex) // Append non-match
|
||||
.append(replacement); // Append replaced address
|
||||
|
||||
lastIndex = endIndex;
|
||||
}
|
||||
// Append rest of the content that didn't match
|
||||
if (lastIndex < content.length()) {
|
||||
output.append(content, lastIndex, content.length());
|
||||
}
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -36,6 +36,7 @@ public class Contributors {
|
|||
new Contributor("Antonok", CODE),
|
||||
new Contributor("Argetan", CODE),
|
||||
new Contributor("Aurelien", CODE, LANG),
|
||||
new Contributor("Binero", CODE),
|
||||
new Contributor("BrainStone", CODE),
|
||||
new Contributor("Catalina", LANG),
|
||||
new Contributor("Elguerrero", LANG),
|
||||
|
@ -107,7 +108,10 @@ public class Contributors {
|
|||
new Contributor("lis2a", LANG),
|
||||
new Contributor("ToxiWoxi", CODE),
|
||||
new Contributor("xlanyleeet", LANG),
|
||||
new Contributor("Jumala9163", LANG)
|
||||
new Contributor("Jumala9163", LANG),
|
||||
new Contributor("Dreeam-qwq", CODE),
|
||||
new Contributor("jhqwqmc", LANG),
|
||||
new Contributor("liuzhen932", LANG)
|
||||
};
|
||||
|
||||
private Contributors() {
|
||||
|
|
|
@ -320,9 +320,9 @@ public class JSONFactory {
|
|||
|
||||
tableEntries.add(Maps.builder(String.class, Object.class)
|
||||
.put("country", geolocation)
|
||||
.put("avg_ping", formatters.decimals().apply(ping.getAverage()) + " ms")
|
||||
.put("min_ping", ping.getMin() + " ms")
|
||||
.put("max_ping", ping.getMax() + " ms")
|
||||
.put("avg_ping", ping.getAverage())
|
||||
.put("min_ping", ping.getMin())
|
||||
.put("max_ping", ping.getMax())
|
||||
.build());
|
||||
}
|
||||
return tableEntries;
|
||||
|
|
|
@ -154,9 +154,9 @@ public class PlayerJSONCreator {
|
|||
private Map<String, Object> createPingGraphJson(PlayerContainer player) {
|
||||
PingGraph pingGraph = graphs.line().pingGraph(player.getUnsafe(PlayerKeys.PING));
|
||||
return Maps.builder(String.class, Object.class)
|
||||
.put("min_ping_series", pingGraph.getMinGraph().getPoints())
|
||||
.put("avg_ping_series", pingGraph.getAvgGraph().getPoints())
|
||||
.put("max_ping_series", pingGraph.getMaxGraph().getPoints())
|
||||
.put("min_ping_series", pingGraph.getMinGraph().getPointArrays())
|
||||
.put("avg_ping_series", pingGraph.getAvgGraph().getPointArrays())
|
||||
.put("max_ping_series", pingGraph.getMaxGraph().getPointArrays())
|
||||
.put("colors", Maps.builder(String.class, String.class)
|
||||
.put("min", theme.getValue(ThemeVal.GRAPH_MIN_PING))
|
||||
.put("avg", theme.getValue(ThemeVal.GRAPH_AVG_PING))
|
||||
|
|
|
@ -42,8 +42,6 @@ import java.util.stream.Collectors;
|
|||
|
||||
/**
|
||||
* Utility for creating jQuery Datatables JSON for a Players Table.
|
||||
* <p>
|
||||
* See https://www.datatables.net/manual/data/orthogonal-data#HTML-5 for sort kinds
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
|
@ -137,12 +135,14 @@ public class PlayersTableJSONCreator {
|
|||
.map(player -> TablePlayerDto.builder()
|
||||
.withUuid(player.getPlayerUUID())
|
||||
.withName(player.getName().orElseGet(() -> player.getPlayerUUID().toString()))
|
||||
.withActivityIndex(player.getCurrentActivityIndex().map(ActivityIndex::getValue).orElse(0.0))
|
||||
.withSessionCount((long) player.getSessionCount().orElse(0))
|
||||
.withPlaytimeActive(player.getActivePlaytime().orElse(null))
|
||||
.withLastSeen(player.getLastSeen().orElse(null))
|
||||
.withRegistered(player.getRegistered().orElse(null))
|
||||
.withCountry(player.getGeolocation().orElse(null))
|
||||
.withExtensionValues(mapToExtensionValues(extensionData.get(player.getPlayerUUID())))
|
||||
.withPing(player.getPing())
|
||||
.build()
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
|
@ -200,7 +200,9 @@ public class PlayersTableJSONCreator {
|
|||
|
||||
Html link = openPlayerPageInNewTab ? Html.LINK_EXTERNAL : Html.LINK;
|
||||
|
||||
putDataEntry(dataJson, link.create(url, StringUtils.replace(StringEscapeUtils.escapeHtml4(name), "\\", "\\\\") /* Backslashes escaped to prevent json errors */), "name");
|
||||
/* Backslashes escaped to prevent json errors */
|
||||
String escapedName = StringUtils.replace(StringEscapeUtils.escapeHtml4(name), "\\", "\\\\");
|
||||
putDataEntry(dataJson, link.create(url, escapedName, escapedName), "name");
|
||||
putDataEntry(dataJson, activityIndex.getValue(), activityString, "index");
|
||||
putDataEntry(dataJson, activePlaytime, numberFormatters.get(FormatType.TIME_MILLISECONDS).apply(activePlaytime), "activePlaytime");
|
||||
putDataEntry(dataJson, loginTimes, "sessions");
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package com.djrapitops.plan.delivery.rendering.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.DateHolder;
|
||||
import com.djrapitops.plan.delivery.domain.DateObj;
|
||||
import com.djrapitops.plan.delivery.domain.mutators.TPSMutator;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
|
@ -55,17 +54,14 @@ import java.util.concurrent.TimeUnit;
|
|||
@Singleton
|
||||
public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<String, Object>> {
|
||||
|
||||
private final Formatter<Long> day;
|
||||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
private final ServerInfo serverInfo;
|
||||
private final ServerSensor<?> serverSensor;
|
||||
|
||||
private final Formatter<Long> timeAmount;
|
||||
private final Formatter<Double> decimals;
|
||||
private final Formatter<Double> percentage;
|
||||
private final ServerUptimeCalculator serverUptimeCalculator;
|
||||
private final Formatter<DateHolder> year;
|
||||
|
||||
@Inject
|
||||
public ServerOverviewJSONCreator(
|
||||
|
@ -82,9 +78,6 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
|
|||
this.serverSensor = serverSensor;
|
||||
this.serverUptimeCalculator = serverUptimeCalculator;
|
||||
|
||||
year = formatters.year();
|
||||
day = formatters.dayLong();
|
||||
timeAmount = formatters.timeAmount();
|
||||
decimals = formatters.decimals();
|
||||
percentage = formatters.percentage();
|
||||
}
|
||||
|
@ -118,7 +111,7 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
|
|||
double averageTPS = tpsMutator.averageTPS();
|
||||
sevenDays.put("average_tps", averageTPS != -1 ? decimals.apply(averageTPS) : GenericLang.UNAVAILABLE.getKey());
|
||||
sevenDays.put("low_tps_spikes", tpsMutator.lowTpsSpikeCount(config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED)));
|
||||
sevenDays.put("downtime", timeAmount.apply(tpsMutator.serverDownTime()));
|
||||
sevenDays.put("downtime", tpsMutator.serverDownTime());
|
||||
|
||||
return sevenDays;
|
||||
}
|
||||
|
@ -137,18 +130,19 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
|
|||
numbers.put("online_players", getOnlinePlayers(serverUUID, db));
|
||||
Optional<DateObj<Integer>> lastPeak = db.query(TPSQueries.fetchPeakPlayerCount(serverUUID, twoDaysAgo));
|
||||
Optional<DateObj<Integer>> allTimePeak = db.query(TPSQueries.fetchAllTimePeakPlayerCount(serverUUID));
|
||||
numbers.put("last_peak_date", lastPeak.map(year).orElse("-"));
|
||||
numbers.put("last_peak_date", lastPeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
|
||||
numbers.put("last_peak_players", lastPeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
|
||||
numbers.put("best_peak_date", allTimePeak.map(year).orElse("-"));
|
||||
numbers.put("best_peak_date", allTimePeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
|
||||
numbers.put("best_peak_players", allTimePeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
|
||||
Long totalPlaytime = db.query(SessionQueries.playtime(0L, now, serverUUID));
|
||||
numbers.put("playtime", timeAmount.apply(totalPlaytime));
|
||||
numbers.put("player_playtime", userCount != 0 ? timeAmount.apply(totalPlaytime / userCount) : "-");
|
||||
numbers.put("playtime", totalPlaytime);
|
||||
numbers.put("player_playtime", userCount != 0 ? totalPlaytime / userCount : "-");
|
||||
numbers.put("sessions", db.query(SessionQueries.sessionCount(0L, now, serverUUID)));
|
||||
numbers.put("player_kills", db.query(KillQueries.playerKillCount(0L, now, serverUUID)));
|
||||
numbers.put("mob_kills", db.query(KillQueries.mobKillCount(0L, now, serverUUID)));
|
||||
numbers.put("deaths", db.query(KillQueries.deathCount(0L, now, serverUUID)));
|
||||
numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID).map(timeAmount)
|
||||
numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID)
|
||||
.map(Object.class::cast)
|
||||
.orElse(GenericLang.UNAVAILABLE.getKey()));
|
||||
|
||||
return numbers;
|
||||
|
@ -171,9 +165,9 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
|
|||
|
||||
Map<String, Object> weeks = new HashMap<>();
|
||||
|
||||
weeks.put("start", day.apply(twoWeeksAgo));
|
||||
weeks.put("midpoint", day.apply(oneWeekAgo));
|
||||
weeks.put("end", day.apply(now));
|
||||
weeks.put("start", twoWeeksAgo);
|
||||
weeks.put("midpoint", oneWeekAgo);
|
||||
weeks.put("end", now);
|
||||
|
||||
Integer uniqueBefore = db.query(PlayerCountQueries.uniquePlayerCount(twoWeeksAgo, oneWeekAgo, serverUUID));
|
||||
Integer uniqueAfter = db.query(PlayerCountQueries.uniquePlayerCount(oneWeekAgo, now, serverUUID));
|
||||
|
@ -199,9 +193,9 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
|
|||
Long playtimeAfter = db.query(SessionQueries.playtime(oneWeekAgo, now, serverUUID));
|
||||
long avgPlaytimeBefore = uniqueBefore != 0 ? playtimeBefore / uniqueBefore : 0L;
|
||||
long avgPlaytimeAfter = uniqueAfter != 0 ? playtimeAfter / uniqueAfter : 0L;
|
||||
Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false, timeAmount);
|
||||
weeks.put("average_playtime_before", timeAmount.apply(avgPlaytimeBefore));
|
||||
weeks.put("average_playtime_after", timeAmount.apply(avgPlaytimeAfter));
|
||||
Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false);
|
||||
weeks.put("average_playtime_before", avgPlaytimeBefore);
|
||||
weeks.put("average_playtime_after", avgPlaytimeAfter);
|
||||
weeks.put("average_playtime_trend", avgPlaytimeTrend);
|
||||
|
||||
Long sessionsBefore = db.query(SessionQueries.sessionCount(twoWeeksAgo, oneWeekAgo, serverUUID));
|
||||
|
|
|
@ -310,6 +310,33 @@ public class GraphJSONCreator {
|
|||
",\"firstDay\":" + 1 + '}';
|
||||
}
|
||||
|
||||
public String networkCalendarJSON() {
|
||||
Database db = dbSystem.getDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
long twoYearsAgo = now - TimeUnit.DAYS.toMillis(730L);
|
||||
int timeZoneOffset = config.getTimeZone().getOffset(now);
|
||||
NavigableMap<Long, Integer> uniquePerDay = db.query(
|
||||
PlayerCountQueries.uniquePlayerCounts(twoYearsAgo, now, timeZoneOffset)
|
||||
);
|
||||
NavigableMap<Long, Integer> newPerDay = db.query(
|
||||
PlayerCountQueries.newPlayerCounts(twoYearsAgo, now, timeZoneOffset)
|
||||
);
|
||||
NavigableMap<Long, Long> playtimePerDay = db.query(
|
||||
SessionQueries.playtimePerDay(twoYearsAgo, now, timeZoneOffset)
|
||||
);
|
||||
NavigableMap<Long, Integer> sessionsPerDay = db.query(
|
||||
SessionQueries.sessionCountPerDay(twoYearsAgo, now, timeZoneOffset)
|
||||
);
|
||||
return "{\"data\":" +
|
||||
graphs.calendar().serverCalendar(
|
||||
uniquePerDay,
|
||||
newPerDay,
|
||||
playtimePerDay,
|
||||
sessionsPerDay
|
||||
).toCalendarSeries() +
|
||||
",\"firstDay\":" + 1 + '}';
|
||||
}
|
||||
|
||||
public Map<String, Object> serverWorldPieJSONAsMap(ServerUUID serverUUID) {
|
||||
Database db = dbSystem.getDatabase();
|
||||
WorldTimes worldTimes = db.query(WorldTimesQueries.fetchServerTotalWorldTimes(serverUUID));
|
||||
|
@ -462,7 +489,7 @@ public class GraphJSONCreator {
|
|||
Integer unknown = joinAddresses.get(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||
if (unknown != null) {
|
||||
joinAddresses.remove(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||
joinAddresses.put(locale.getString(GenericLang.UNKNOWN).toLowerCase(), unknown);
|
||||
joinAddresses.put(GenericLang.UNKNOWN.getKey(), unknown);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -510,7 +537,7 @@ public class GraphJSONCreator {
|
|||
List<ServerSpecificLineGraph> proxyGraphs = new ArrayList<>();
|
||||
for (Server proxy : db.query(ServerQueries.fetchProxyServers())) {
|
||||
ServerUUID proxyUUID = proxy.getUuid();
|
||||
List<Double[]> points = Lists.map(
|
||||
List<Number[]> points = Lists.map(
|
||||
db.query(TPSQueries.fetchPlayersOnlineOfServer(halfYearAgo, now, proxyUUID)),
|
||||
point -> Point.fromDateObj(point).toArray()
|
||||
);
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.djrapitops.plan.delivery.rendering.json.graphs.HighChart;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This is a LineGraph for any set of Points, thus it is Abstract.
|
||||
|
@ -80,6 +81,10 @@ public class LineGraph implements HighChart {
|
|||
return points;
|
||||
}
|
||||
|
||||
public List<Number[]> getPointArrays() {
|
||||
return getPoints().stream().map(Point::toArray).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void addMissingPoints(StringBuilder arrayBuilder, Long lastX, long date) {
|
||||
long iterate = lastX + gapStrategy.diffToFirstGapPointMs;
|
||||
while (iterate < date) {
|
||||
|
|
|
@ -75,7 +75,7 @@ public class Point {
|
|||
"y=" + y + '}';
|
||||
}
|
||||
|
||||
public Double[] toArray() {
|
||||
return new Double[]{x, y};
|
||||
public Number[] toArray() {
|
||||
return new Number[]{x, y};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ public class PieGraphFactory {
|
|||
|
||||
public Pie activityPie(Map<String, Integer> activityData) {
|
||||
String[] colors = theme.getPieColors(ThemeVal.GRAPH_ACTIVITY_PIE);
|
||||
return new ActivityPie(activityData, colors, ActivityIndex.getGroups(locale));
|
||||
return new ActivityPie(activityData, colors, ActivityIndex.getDefaultGroupLangKeys());
|
||||
}
|
||||
|
||||
public Pie serverPreferencePie(Map<ServerUUID, String> serverNames, Map<ServerUUID, WorldTimes> serverWorldTimes) {
|
||||
|
|
|
@ -78,4 +78,9 @@ public class SpecialGraphFactory {
|
|||
}
|
||||
}
|
||||
|
||||
public Map<String, String> getGeocodes() {
|
||||
if (geoCodes == null) prepareGeocodes();
|
||||
return geoCodes;
|
||||
}
|
||||
|
||||
}
|
|
@ -53,6 +53,6 @@ public class StackGraphFactory {
|
|||
|
||||
public StackGraph activityStackGraph(DateMap<Map<String, Integer>> activityData) {
|
||||
String[] colors = theme.getPieColors(ThemeVal.GRAPH_ACTIVITY_PIE);
|
||||
return new ActivityStackGraph(activityData, colors, dayFormatter, ActivityIndex.getGroups(locale));
|
||||
return new ActivityStackGraph(activityData, colors, dayFormatter, ActivityIndex.getDefaultGroupLangKeys());
|
||||
}
|
||||
}
|
|
@ -16,9 +16,7 @@
|
|||
*/
|
||||
package com.djrapitops.plan.delivery.rendering.json.network;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.DateHolder;
|
||||
import com.djrapitops.plan.delivery.domain.DateObj;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.json.Trend;
|
||||
import com.djrapitops.plan.gathering.ServerSensor;
|
||||
|
@ -50,14 +48,11 @@ import java.util.concurrent.TimeUnit;
|
|||
@Singleton
|
||||
public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<String, Object>> {
|
||||
|
||||
private final Formatter<Long> day;
|
||||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
private final ServerInfo serverInfo;
|
||||
private final ServerSensor<?> serverSensor;
|
||||
private final Formatter<Long> timeAmount;
|
||||
private final ServerUptimeCalculator serverUptimeCalculator;
|
||||
private final Formatter<DateHolder> year;
|
||||
|
||||
@Inject
|
||||
public NetworkOverviewJSONCreator(
|
||||
|
@ -73,10 +68,6 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
|
|||
this.serverInfo = serverInfo;
|
||||
this.serverSensor = serverSensor;
|
||||
this.serverUptimeCalculator = serverUptimeCalculator;
|
||||
|
||||
year = formatters.year();
|
||||
day = formatters.dayLong();
|
||||
timeAmount = formatters.timeAmount();
|
||||
}
|
||||
|
||||
public Map<String, Object> createJSONAsMap() {
|
||||
|
@ -122,17 +113,18 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
|
|||
ServerUUID serverUUID = serverInfo.getServerUUID();
|
||||
Optional<DateObj<Integer>> lastPeak = db.query(TPSQueries.fetchPeakPlayerCount(serverUUID, twoDaysAgo));
|
||||
Optional<DateObj<Integer>> allTimePeak = db.query(TPSQueries.fetchAllTimePeakPlayerCount(serverUUID));
|
||||
numbers.put("last_peak_date", lastPeak.map(year).orElse("-"));
|
||||
numbers.put("last_peak_date", lastPeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
|
||||
numbers.put("last_peak_players", lastPeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
|
||||
numbers.put("best_peak_date", allTimePeak.map(year).orElse("-"));
|
||||
numbers.put("best_peak_date", allTimePeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
|
||||
numbers.put("best_peak_players", allTimePeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
|
||||
Long totalPlaytime = db.query(SessionQueries.playtime(0L, now));
|
||||
numbers.put("playtime", timeAmount.apply(totalPlaytime));
|
||||
numbers.put("player_playtime", userCount != 0 ? timeAmount.apply(totalPlaytime / userCount) : "-");
|
||||
numbers.put("playtime", totalPlaytime);
|
||||
numbers.put("player_playtime", userCount != 0 ? totalPlaytime / userCount : "-");
|
||||
Long sessionCount = db.query(SessionQueries.sessionCount(0L, now));
|
||||
numbers.put("sessions", sessionCount);
|
||||
numbers.put("session_length_avg", sessionCount != 0 ? timeAmount.apply(totalPlaytime / sessionCount) : "-");
|
||||
numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID).map(timeAmount)
|
||||
numbers.put("session_length_avg", sessionCount != 0 ? totalPlaytime / sessionCount : "-");
|
||||
numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID)
|
||||
.map(Object.class::cast)
|
||||
.orElse(GenericLang.UNAVAILABLE.getKey()));
|
||||
|
||||
return numbers;
|
||||
|
@ -147,9 +139,9 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
|
|||
|
||||
Map<String, Object> weeks = new HashMap<>();
|
||||
|
||||
weeks.put("start", day.apply(twoWeeksAgo));
|
||||
weeks.put("midpoint", day.apply(oneWeekAgo));
|
||||
weeks.put("end", day.apply(now));
|
||||
weeks.put("start", twoWeeksAgo);
|
||||
weeks.put("midpoint", oneWeekAgo);
|
||||
weeks.put("end", now);
|
||||
|
||||
Integer uniqueBefore = db.query(PlayerCountQueries.uniquePlayerCount(twoWeeksAgo, oneWeekAgo));
|
||||
Integer uniqueAfter = db.query(PlayerCountQueries.uniquePlayerCount(oneWeekAgo, now));
|
||||
|
@ -175,9 +167,9 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
|
|||
Long playtimeAfter = db.query(SessionQueries.playtime(oneWeekAgo, now));
|
||||
long avgPlaytimeBefore = uniqueBefore != 0 ? playtimeBefore / uniqueBefore : 0L;
|
||||
long avgPlaytimeAfter = uniqueAfter != 0 ? playtimeAfter / uniqueAfter : 0L;
|
||||
Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false, timeAmount);
|
||||
weeks.put("average_playtime_before", timeAmount.apply(avgPlaytimeBefore));
|
||||
weeks.put("average_playtime_after", timeAmount.apply(avgPlaytimeAfter));
|
||||
Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false);
|
||||
weeks.put("average_playtime_before", avgPlaytimeBefore);
|
||||
weeks.put("average_playtime_after", avgPlaytimeAfter);
|
||||
weeks.put("average_playtime_trend", avgPlaytimeTrend);
|
||||
|
||||
Long sessionsBefore = db.query(SessionQueries.sessionCount(twoWeeksAgo, oneWeekAgo));
|
||||
|
@ -189,9 +181,9 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
|
|||
|
||||
long avgSessionLengthBefore = sessionsBefore != 0 ? playtimeBefore / sessionsBefore : 0;
|
||||
long avgSessionLengthAfter = sessionsAfter != 0 ? playtimeAfter / sessionsAfter : 0;
|
||||
Trend avgSessionLengthTrend = new Trend(avgSessionLengthBefore, avgSessionLengthAfter, false, timeAmount);
|
||||
weeks.put("session_length_average_before", timeAmount.apply(avgSessionLengthBefore));
|
||||
weeks.put("session_length_average_after", timeAmount.apply(avgSessionLengthAfter));
|
||||
Trend avgSessionLengthTrend = new Trend(avgSessionLengthBefore, avgSessionLengthAfter, false);
|
||||
weeks.put("session_length_average_before", avgSessionLengthBefore);
|
||||
weeks.put("session_length_average_after", avgSessionLengthAfter);
|
||||
weeks.put("session_length_average_trend", avgSessionLengthTrend);
|
||||
|
||||
return weeks;
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
*/
|
||||
package com.djrapitops.plan.delivery.rendering.pages;
|
||||
|
||||
import com.djrapitops.plan.delivery.rendering.BundleAddressCorrection;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import com.djrapitops.plan.delivery.web.ResourceService;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
||||
import com.djrapitops.plan.delivery.web.resource.WebResource;
|
||||
import com.djrapitops.plan.delivery.webserver.Addresses;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
|
@ -50,7 +50,7 @@ public class PageFactory {
|
|||
private final Lazy<PublicHtmlFiles> publicHtmlFiles;
|
||||
private final Lazy<Theme> theme;
|
||||
private final Lazy<DBSystem> dbSystem;
|
||||
private final Lazy<Addresses> addresses;
|
||||
private final Lazy<BundleAddressCorrection> bundleAddressCorrection;
|
||||
private static final String ERROR_HTML_FILE = "error.html";
|
||||
|
||||
@Inject
|
||||
|
@ -61,14 +61,14 @@ public class PageFactory {
|
|||
Lazy<Theme> theme,
|
||||
Lazy<DBSystem> dbSystem,
|
||||
Lazy<ServerInfo> serverInfo,
|
||||
Lazy<Addresses> addresses
|
||||
Lazy<BundleAddressCorrection> bundleAddressCorrection
|
||||
) {
|
||||
this.versionChecker = versionChecker;
|
||||
this.files = files;
|
||||
this.publicHtmlFiles = publicHtmlFiles;
|
||||
this.theme = theme;
|
||||
this.dbSystem = dbSystem;
|
||||
this.addresses = addresses;
|
||||
this.bundleAddressCorrection = bundleAddressCorrection;
|
||||
}
|
||||
|
||||
public Page playersPage() throws IOException {
|
||||
|
@ -81,18 +81,12 @@ public class PageFactory {
|
|||
WebResource resource = ResourceService.getInstance().getResource(
|
||||
"Plan", fileName, () -> getPublicHtmlOrJarResource(fileName)
|
||||
);
|
||||
return new ReactPage(getBasePath(), resource);
|
||||
return new ReactPage(bundleAddressCorrection.get(), resource);
|
||||
} catch (UncheckedIOException readFail) {
|
||||
throw readFail.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
private String getBasePath() {
|
||||
String address = addresses.get().getMainAddress()
|
||||
.orElseGet(addresses.get()::getFallbackLocalhostAddress);
|
||||
return addresses.get().getBasePath(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a server page.
|
||||
*
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
*/
|
||||
package com.djrapitops.plan.delivery.rendering.pages;
|
||||
|
||||
import com.djrapitops.plan.delivery.rendering.BundleAddressCorrection;
|
||||
import com.djrapitops.plan.delivery.web.resource.WebResource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Represents React index.html.
|
||||
|
@ -26,20 +26,17 @@ import org.apache.commons.lang3.StringUtils;
|
|||
*/
|
||||
public class ReactPage implements Page {
|
||||
|
||||
private final String basePath;
|
||||
private final BundleAddressCorrection bundleAddressCorrection;
|
||||
private final WebResource reactHtml;
|
||||
|
||||
public ReactPage(String basePath, WebResource reactHtml) {
|
||||
this.basePath = basePath;
|
||||
public ReactPage(BundleAddressCorrection bundleAddressCorrection, WebResource reactHtml) {
|
||||
this.bundleAddressCorrection = bundleAddressCorrection;
|
||||
this.reactHtml = reactHtml;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toHtml() {
|
||||
return StringUtils.replaceEach(
|
||||
reactHtml.asString(),
|
||||
new String[]{"/static", "/pageExtensionApi.js"},
|
||||
new String[]{basePath + "/static", basePath + "/pageExtensionApi.js"});
|
||||
return bundleAddressCorrection.correctAddressForWebserver(reactHtml.asString(), "index.html");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,8 +20,8 @@ import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
|||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.transactions.GrantWebPermissionToGroupsWithPermissionTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.StoreMissingWebPermissionsTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.webuser.GrantWebPermissionToGroupsWithPermissionTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.webuser.StoreMissingWebPermissionsTransaction;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import net.playeranalytics.plugin.server.PluginLogger;
|
||||
|
||||
|
@ -64,6 +64,8 @@ public class ResolverSvc implements ResolverService {
|
|||
public void registerResolver(String pluginName, String start, Resolver resolver) {
|
||||
basicResolvers.add(new Container(pluginName, checking -> checking.startsWith(start), resolver, start));
|
||||
Collections.sort(basicResolvers);
|
||||
Set<String> usedWebPermissions = resolver.usedWebPermissions();
|
||||
dbSystem.getDatabase().executeTransaction(new StoreMissingWebPermissionsTransaction(usedWebPermissions));
|
||||
if (config.isTrue(PluginSettings.DEV_MODE)) {
|
||||
logger.info("Registered basic resolver '" + start + "' for plugin " + pluginName);
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ public class Addresses {
|
|||
}
|
||||
|
||||
public Optional<String> getMainAddress() {
|
||||
Optional<String> proxyServerAddress = getProxyServerAddress();
|
||||
Optional<String> proxyServerAddress = getAnyValidServerAddress();
|
||||
return proxyServerAddress.isPresent() ? proxyServerAddress : getAccessAddress();
|
||||
}
|
||||
|
||||
|
@ -112,8 +112,21 @@ public class Addresses {
|
|||
.findAny();
|
||||
}
|
||||
|
||||
public Optional<String> getAnyValidServerAddress() {
|
||||
return dbSystem.getDatabase().query(ServerQueries.fetchPlanServerInformationCollection())
|
||||
.stream()
|
||||
.map(Server::getWebAddress)
|
||||
.filter(this::isValidAddress)
|
||||
.findAny();
|
||||
}
|
||||
|
||||
private boolean isValidAddress(String address) {
|
||||
return address != null && !address.isEmpty() && !"0.0.0.0".equals(address);
|
||||
return address != null
|
||||
&& !address.isEmpty()
|
||||
&& !"0.0.0.0".equals(address)
|
||||
&& !"https://www.example.address".equals(address)
|
||||
&& !"http://www.example.address".equals(address)
|
||||
&& !"http://localhost:0".equals(address);
|
||||
}
|
||||
|
||||
public Optional<String> getServerPropertyIP() {
|
||||
|
|
|
@ -20,6 +20,8 @@ import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
|||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.locale.lang.PluginLang;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import net.playeranalytics.plugin.server.PluginLogger;
|
||||
|
@ -34,6 +36,7 @@ import java.io.IOException;
|
|||
public class NonProxyWebserverDisableChecker implements Runnable {
|
||||
|
||||
private final PlanConfig config;
|
||||
private final Locale locale;
|
||||
private final Addresses addresses;
|
||||
private final WebServerSystem webServerSystem;
|
||||
private final PluginLogger logger;
|
||||
|
@ -41,12 +44,14 @@ public class NonProxyWebserverDisableChecker implements Runnable {
|
|||
|
||||
public NonProxyWebserverDisableChecker(
|
||||
PlanConfig config,
|
||||
Locale locale,
|
||||
Addresses addresses,
|
||||
WebServerSystem webServerSystem,
|
||||
PluginLogger logger,
|
||||
ErrorLogger errorLogger
|
||||
) {
|
||||
this.config = config;
|
||||
this.locale = locale;
|
||||
this.addresses = addresses;
|
||||
this.webServerSystem = webServerSystem;
|
||||
this.logger = logger;
|
||||
|
@ -58,7 +63,7 @@ public class NonProxyWebserverDisableChecker implements Runnable {
|
|||
if (config.isFalse(PluginSettings.PROXY_COPY_CONFIG)) return;
|
||||
|
||||
addresses.getProxyServerAddress().ifPresent(address -> {
|
||||
logger.info("Proxy server detected in the database - Proxy Webserver address is '" + address + "'.");
|
||||
logger.info(locale.getString(PluginLang.ENABLE_NOTIFY_PROXY_ADDRESS, address));
|
||||
WebServer webServer = webServerSystem.getWebServer();
|
||||
|
||||
if (webServer.isEnabled()) {
|
||||
|
@ -68,12 +73,12 @@ public class NonProxyWebserverDisableChecker implements Runnable {
|
|||
}
|
||||
|
||||
private void disableWebserver(WebServer webServer) {
|
||||
logger.warn("Disabling Webserver on this server - You can override this behavior by setting '" + PluginSettings.PROXY_COPY_CONFIG.getPath() + "' to false.");
|
||||
logger.warn(locale.getString(PluginLang.ENABLE_NOTIFY_PROXY_DISABLED_WEBSERVER, PluginSettings.PROXY_COPY_CONFIG.getPath()));
|
||||
webServer.disable();
|
||||
try {
|
||||
config.set(WebserverSettings.DISABLED, true);
|
||||
config.save();
|
||||
logger.warn("Note: Set '" + WebserverSettings.DISABLED.getPath() + "' to true");
|
||||
logger.warn(locale.getString(PluginLang.ENABLE_NOTIFY_SETTING_CHANGE, WebserverSettings.DISABLED.getPath(), "true"));
|
||||
} catch (IOException e) {
|
||||
errorLogger.warn(e, ErrorContext.builder()
|
||||
.whatToDo("Set '" + WebserverSettings.DISABLED.getPath() + "' to true manually.")
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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.webserver;
|
||||
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Simple guard against DDoS attacks to single endpoint.
|
||||
* <p>
|
||||
* This only protects against a DDoS that doesn't follow redirects.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class RateLimitGuard {
|
||||
|
||||
private static final int ATTEMPT_LIMIT = 30;
|
||||
private final Cache<String, Integer> requests = Caffeine.newBuilder()
|
||||
.expireAfterWrite(120, TimeUnit.SECONDS)
|
||||
.build();
|
||||
private final Cache<String, String> lastRequestPath = Caffeine.newBuilder()
|
||||
.expireAfterWrite(120, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
public boolean shouldPreventRequest(@Untrusted String accessor) {
|
||||
Integer attempts = requests.getIfPresent(accessor);
|
||||
if (attempts == null) return false;
|
||||
// Too many attempts, forbid further attempts.
|
||||
return attempts >= ATTEMPT_LIMIT;
|
||||
}
|
||||
|
||||
public void increaseAttemptCount(@Untrusted String requestPath, @Untrusted String accessor) {
|
||||
String previous = lastRequestPath.getIfPresent(accessor);
|
||||
if (!Objects.equals(previous, requestPath)) {
|
||||
resetAttemptCount(accessor);
|
||||
}
|
||||
|
||||
Integer attempts = requests.getIfPresent(accessor);
|
||||
if (attempts == null) {
|
||||
attempts = 0;
|
||||
}
|
||||
|
||||
lastRequestPath.put(accessor, requestPath);
|
||||
requests.put(accessor, attempts + 1);
|
||||
}
|
||||
|
||||
public void resetAttemptCount(@Untrusted String accessor) {
|
||||
// previous request changed
|
||||
requests.cleanUp();
|
||||
requests.invalidate(accessor);
|
||||
}
|
||||
|
||||
public static class Disabled extends RateLimitGuard {
|
||||
@Override
|
||||
public boolean shouldPreventRequest(String accessor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increaseAttemptCount(String requestPath, String accessor) { /* Disabled */ }
|
||||
|
||||
@Override
|
||||
public void resetAttemptCount(String accessor) { /* Disabled */ }
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ import com.djrapitops.plan.delivery.domain.container.PlayerContainer;
|
|||
import com.djrapitops.plan.delivery.domain.keys.PlayerKeys;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.BundleAddressCorrection;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Family;
|
||||
import com.djrapitops.plan.delivery.rendering.html.icon.Icon;
|
||||
import com.djrapitops.plan.delivery.rendering.pages.Page;
|
||||
|
@ -75,6 +76,7 @@ public class ResponseFactory {
|
|||
private final DBSystem dbSystem;
|
||||
private final Theme theme;
|
||||
private final Lazy<Addresses> addresses;
|
||||
private final Lazy<BundleAddressCorrection> bundleAddressCorrection;
|
||||
private final Formatter<Long> httpLastModifiedFormatter;
|
||||
|
||||
@Inject
|
||||
|
@ -86,7 +88,8 @@ public class ResponseFactory {
|
|||
DBSystem dbSystem,
|
||||
Formatters formatters,
|
||||
Theme theme,
|
||||
Lazy<Addresses> addresses
|
||||
Lazy<Addresses> addresses,
|
||||
Lazy<BundleAddressCorrection> bundleAddressCorrection
|
||||
) {
|
||||
this.files = files;
|
||||
this.publicHtmlFiles = publicHtmlFiles;
|
||||
|
@ -97,6 +100,7 @@ public class ResponseFactory {
|
|||
this.addresses = addresses;
|
||||
|
||||
httpLastModifiedFormatter = formatters.httpLastModifiedLong();
|
||||
this.bundleAddressCorrection = bundleAddressCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,9 +236,7 @@ public class ResponseFactory {
|
|||
String content = UnaryChain.of(resource.asString())
|
||||
.chain(this::replaceMainAddressPlaceholder)
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(contents -> StringUtils.replace(contents,
|
||||
".p=\"/\"",
|
||||
".p=\"" + getBasePath() + "/\""))
|
||||
.chain(contents -> bundleAddressCorrection.get().correctAddressForWebserver(contents, fileName))
|
||||
.apply();
|
||||
ResponseBuilder responseBuilder = Response.builder()
|
||||
.setMimeType(MimeType.JS)
|
||||
|
@ -244,7 +246,7 @@ public class ResponseFactory {
|
|||
if (fileName.contains(STATIC_BUNDLE_FOLDER)) {
|
||||
resource.getLastModified().ifPresent(lastModified -> responseBuilder
|
||||
// Can't cache main bundle in browser since base path might change
|
||||
.setHeader(HttpHeader.CACHE_CONTROL.asString(), fileName.contains("main") ? CacheStrategy.CHECK_ETAG : CacheStrategy.CACHE_IN_BROWSER)
|
||||
.setHeader(HttpHeader.CACHE_CONTROL.asString(), fileName.contains("index") ? CacheStrategy.CHECK_ETAG : CacheStrategy.CACHE_IN_BROWSER)
|
||||
.setHeader(HttpHeader.LAST_MODIFIED.asString(), httpLastModifiedFormatter.apply(lastModified))
|
||||
.setHeader(HttpHeader.ETAG.asString(), lastModified));
|
||||
}
|
||||
|
@ -254,12 +256,6 @@ public class ResponseFactory {
|
|||
}
|
||||
}
|
||||
|
||||
private String getBasePath() {
|
||||
String address = addresses.get().getMainAddress()
|
||||
.orElseGet(addresses.get()::getFallbackLocalhostAddress);
|
||||
return addresses.get().getBasePath(address);
|
||||
}
|
||||
|
||||
private String replaceMainAddressPlaceholder(String resource) {
|
||||
String address = addresses.get().getAccessAddress()
|
||||
.orElseGet(addresses.get()::getFallbackLocalhostAddress);
|
||||
|
@ -275,7 +271,7 @@ public class ResponseFactory {
|
|||
WebResource resource = getPublicOrJarResource(fileName);
|
||||
String content = UnaryChain.of(resource.asString())
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(contents -> StringUtils.replace(contents, "/static", getBasePath() + "/static"))
|
||||
.chain(contents -> bundleAddressCorrection.get().correctAddressForWebserver(contents, fileName))
|
||||
.apply();
|
||||
|
||||
ResponseBuilder responseBuilder = Response.builder()
|
||||
|
@ -479,6 +475,14 @@ public class ResponseFactory {
|
|||
.build();
|
||||
}
|
||||
|
||||
public Response failedRateLimit403() {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.HTML)
|
||||
.setContent("<h1>403 Forbidden</h1><p>You are being rate-limited.</p>")
|
||||
.setStatus(403)
|
||||
.build();
|
||||
}
|
||||
|
||||
public Response ipWhitelist403(@Untrusted String accessor) {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.HTML)
|
||||
|
|
|
@ -147,8 +147,9 @@ public class ResponseResolver {
|
|||
String plugin = "Plan";
|
||||
resolverService.registerResolver(plugin, "/robots.txt", fileResolver(responseFactory::robotsResponse));
|
||||
resolverService.registerResolver(plugin, "/manifest.json", fileResolver(() -> responseFactory.jsonFileResponse("manifest.json")));
|
||||
resolverService.registerResolver(plugin, "/asset-manifest.json", fileResolver(() -> responseFactory.jsonFileResponse("asset-manifest.json")));
|
||||
resolverService.registerResolver(plugin, "/favicon.ico", fileResolver(responseFactory::faviconResponse));
|
||||
resolverService.registerResolver(plugin, "/logo192.png", fileResolver(() -> responseFactory.imageResponse("logo192.png")));
|
||||
resolverService.registerResolver(plugin, "/logo512.png", fileResolver(() -> responseFactory.imageResponse("logo512.png")));
|
||||
resolverService.registerResolver(plugin, "/pageExtensionApi.js", fileResolver(() -> responseFactory.javaScriptResponse("pageExtensionApi.js")));
|
||||
|
||||
resolverService.registerResolver(plugin, "/query", queryPageResolver);
|
||||
|
|
|
@ -61,6 +61,6 @@ public enum DataID {
|
|||
|
||||
public String of(ServerUUID serverUUID) {
|
||||
if (serverUUID == null) return name();
|
||||
return name() + '-' + serverUUID;
|
||||
return name() + "_" + serverUUID;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,8 +75,14 @@ public class AccessLogger {
|
|||
}
|
||||
}
|
||||
try {
|
||||
long timestamp = internalRequest.getTimestamp();
|
||||
String accessAddress = internalRequest.getAccessAddress(webserverConfiguration);
|
||||
String method = internalRequest.getMethod();
|
||||
method = method != null ? method : "?";
|
||||
String url = StoreRequestTransaction.getTruncatedURI(request, internalRequest);
|
||||
int responseCode = response.getCode();
|
||||
dbSystem.getDatabase().executeTransaction(
|
||||
new StoreRequestTransaction(webserverConfiguration, internalRequest, request, response)
|
||||
new StoreRequestTransaction(timestamp, accessAddress, method, url, responseCode)
|
||||
);
|
||||
} catch (CompletionException | DBOpException e) {
|
||||
errorLogger.warn(e, ErrorContext.builder()
|
||||
|
|
|
@ -81,4 +81,6 @@ public interface InternalRequest {
|
|||
}
|
||||
return authenticationExtractor.extractAuthentication(this);
|
||||
}
|
||||
|
||||
String getRequestedPath();
|
||||
}
|
||||
|
|
|
@ -129,6 +129,11 @@ public class JettyInternalRequest implements InternalRequest {
|
|||
return baseRequest.getRequestURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestedPath() {
|
||||
return baseRequest.getHttpURI().getDecodedPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JettyInternalRequest{" +
|
||||
|
|
|
@ -19,6 +19,7 @@ package com.djrapitops.plan.delivery.webserver.http;
|
|||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.webserver.PassBruteForceGuard;
|
||||
import com.djrapitops.plan.delivery.webserver.RateLimitGuard;
|
||||
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||
import com.djrapitops.plan.delivery.webserver.ResponseResolver;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||
|
@ -40,6 +41,7 @@ public class RequestHandler {
|
|||
private final ResponseResolver responseResolver;
|
||||
|
||||
private final PassBruteForceGuard bruteForceGuard;
|
||||
private final RateLimitGuard rateLimitGuard;
|
||||
private final AccessLogger accessLogger;
|
||||
|
||||
@Inject
|
||||
|
@ -50,15 +52,23 @@ public class RequestHandler {
|
|||
this.accessLogger = accessLogger;
|
||||
|
||||
bruteForceGuard = new PassBruteForceGuard();
|
||||
rateLimitGuard = new RateLimitGuard();
|
||||
}
|
||||
|
||||
public Response getResponse(InternalRequest internalRequest) {
|
||||
@Untrusted String accessAddress = internalRequest.getAccessAddress(webserverConfiguration);
|
||||
@Untrusted String requestedPath = internalRequest.getRequestedPath();
|
||||
rateLimitGuard.increaseAttemptCount(requestedPath, accessAddress);
|
||||
|
||||
boolean blocked = false;
|
||||
Response response;
|
||||
@Untrusted Request request = null;
|
||||
if (bruteForceGuard.shouldPreventRequest(accessAddress)) {
|
||||
response = responseFactory.failedLoginAttempts403();
|
||||
blocked = true;
|
||||
} else if (rateLimitGuard.shouldPreventRequest(accessAddress)) {
|
||||
response = responseFactory.failedRateLimit403();
|
||||
blocked = true;
|
||||
} else if (!webserverConfiguration.getAllowedIpList().isAllowed(accessAddress)) {
|
||||
webserverConfiguration.getWebserverLogMessages()
|
||||
.warnAboutWhitelistBlock(accessAddress, internalRequest.getRequestedURIString());
|
||||
|
@ -77,7 +87,9 @@ public class RequestHandler {
|
|||
response.getHeaders().putIfAbsent("Access-Control-Allow-Credentials", "true");
|
||||
response.getHeaders().putIfAbsent("X-Robots-Tag", "noindex, nofollow");
|
||||
|
||||
accessLogger.log(internalRequest, request, response);
|
||||
if (!blocked) {
|
||||
accessLogger.log(internalRequest, request, response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ import java.util.Optional;
|
|||
@Singleton
|
||||
public class StaticResourceResolver implements NoAuthResolver {
|
||||
|
||||
private static final String PART_REGEX = "(vendor|css|js|img|static)";
|
||||
private static final String PART_REGEX = "(static)";
|
||||
public static final String PATH_REGEX = "^.*/" + PART_REGEX + "/.*";
|
||||
|
||||
private final ResponseFactory responseFactory;
|
||||
|
|
|
@ -214,7 +214,7 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||
case GRAPH_OPTIMIZED_PERFORMANCE:
|
||||
return List.of(WebPermission.PAGE_SERVER_PERFORMANCE_GRAPHS, WebPermission.PAGE_NETWORK_PERFORMANCE);
|
||||
case GRAPH_ONLINE:
|
||||
return List.of(WebPermission.PAGE_SERVER_OVERVIEW_PLAYERS_ONLINE_GRAPH);
|
||||
return List.of(WebPermission.PAGE_SERVER_OVERVIEW_PLAYERS_ONLINE_GRAPH, WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_ONLINE);
|
||||
case GRAPH_UNIQUE_NEW:
|
||||
return List.of(WebPermission.PAGE_SERVER_ONLINE_ACTIVITY_GRAPHS_DAY_BY_DAY);
|
||||
case GRAPH_HOURLY_UNIQUE_NEW:
|
||||
|
@ -250,6 +250,8 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_DAY_BY_DAY);
|
||||
case GRAPH_HOURLY_UNIQUE_NEW:
|
||||
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_HOUR_BY_HOUR);
|
||||
case GRAPH_CALENDAR:
|
||||
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_CALENDAR);
|
||||
case GRAPH_SERVER_PIE:
|
||||
return List.of(WebPermission.PAGE_NETWORK_SESSIONS_SERVER_PIE);
|
||||
case GRAPH_WORLD_MAP:
|
||||
|
@ -313,6 +315,8 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||
return graphJSON.uniqueAndNewGraphJSON();
|
||||
case GRAPH_HOURLY_UNIQUE_NEW:
|
||||
return graphJSON.hourlyUniqueAndNewGraphJSON();
|
||||
case GRAPH_CALENDAR:
|
||||
return graphJSON.networkCalendarJSON();
|
||||
case GRAPH_SERVER_PIE:
|
||||
return graphJSON.serverPreferencePieJSONAsMap();
|
||||
case GRAPH_HOSTNAME_PIE:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue