mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-03-12 22:59:26 +01:00
2360/plugin versions (#3249)
* Add methods to gather plugin versions from servers * Gathering and storage for plugin version history * Test plugin gathering * Test plugin metadata storage * /v1/pluginHistory endpoint * Plugin history tab * Plugin history to performance tab * Possibly fix ConfigChange.MovedValue being applied all the time * Updated locale files * Export pluginHistory for server page * Add plugin history to network page * Access control and improvements * Remove pluginHistory from export since it now requires auth * Fix access visibility tests * Fix VelocitySensor during test Affects issues: - Close #2360
This commit is contained in:
parent
61db13626b
commit
5a2bdaf6ba
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -55,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"),
|
||||
@ -90,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"),
|
||||
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.http.WebServer;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.json.metadata.PreferencesJSONResolver;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.json.metadata.StorePreferencesJSONResolver;
|
||||
import com.djrapitops.plan.delivery.webserver.resolver.json.plugins.PluginHistoryJSONResolver;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import dagger.Lazy;
|
||||
|
||||
@ -49,6 +50,7 @@ public class RootJSONResolver {
|
||||
|
||||
private final CompositeResolver.Builder readOnlyResourcesBuilder;
|
||||
private final StorePreferencesJSONResolver storePreferencesJSONResolver;
|
||||
private final PluginHistoryJSONResolver pluginHistoryJSONResolver;
|
||||
private CompositeResolver resolver;
|
||||
|
||||
@Inject
|
||||
@ -84,6 +86,7 @@ public class RootJSONResolver {
|
||||
ExtensionJSONResolver extensionJSONResolver,
|
||||
RetentionJSONResolver retentionJSONResolver,
|
||||
PlayerJoinAddressJSONResolver playerJoinAddressJSONResolver,
|
||||
PluginHistoryJSONResolver pluginHistoryJSONResolver,
|
||||
|
||||
PreferencesJSONResolver preferencesJSONResolver,
|
||||
StorePreferencesJSONResolver storePreferencesJSONResolver,
|
||||
@ -127,6 +130,7 @@ public class RootJSONResolver {
|
||||
|
||||
this.webServer = webServer;
|
||||
// These endpoints require authentication to be enabled.
|
||||
this.pluginHistoryJSONResolver = pluginHistoryJSONResolver;
|
||||
this.webGroupJSONResolver = webGroupJSONResolver;
|
||||
this.webGroupPermissionJSONResolver = webGroupPermissionJSONResolver;
|
||||
this.webPermissionJSONResolver = webPermissionJSONResolver;
|
||||
@ -149,6 +153,7 @@ public class RootJSONResolver {
|
||||
.add("saveGroupPermissions", webGroupSaveJSONResolver)
|
||||
.add("deleteGroup", webGroupDeleteJSONResolver)
|
||||
.add("storePreferences", storePreferencesJSONResolver)
|
||||
.add("pluginHistory", pluginHistoryJSONResolver)
|
||||
.build();
|
||||
} else {
|
||||
resolver = readOnlyResourcesBuilder.build();
|
||||
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.resolver.json.plugins;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.PluginHistoryMetadata;
|
||||
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.PluginHistoryDto;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.PluginMetadataQueries;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Endpoint for getting plugin version history.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
@Path("/v1/pluginHistory")
|
||||
public class PluginHistoryJSONResolver implements Resolver {
|
||||
|
||||
private final DBSystem dbSystem;
|
||||
private final Identifiers identifiers;
|
||||
|
||||
@Inject
|
||||
public PluginHistoryJSONResolver(DBSystem dbSystem, Identifiers identifiers) {
|
||||
this.dbSystem = dbSystem;
|
||||
this.identifiers = identifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
return request.getUser()
|
||||
.map(user -> user.hasPermission(WebPermission.PAGE_NETWORK_PLUGIN_HISTORY)
|
||||
|| user.hasPermission(WebPermission.PAGE_SERVER_PLUGIN_HISTORY))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Operation(
|
||||
description = "Get plugin history for a server since installation of Plan.",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON,
|
||||
schema = @Schema(implementation = PluginHistoryDto.class))),
|
||||
},
|
||||
parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = {
|
||||
@ExampleObject("Server 1"),
|
||||
@ExampleObject("1"),
|
||||
@ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"),
|
||||
}),
|
||||
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
|
||||
)
|
||||
@GET
|
||||
public Optional<Response> resolve(@Untrusted Request request) {
|
||||
return Optional.of(getResponse(request));
|
||||
}
|
||||
|
||||
private Response getResponse(@Untrusted Request request) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
|
||||
List<PluginHistoryMetadata> history = dbSystem.getDatabase().query(PluginMetadataQueries.getPluginHistory(serverUUID));
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
.setJSONContent(new PluginHistoryDto(history))
|
||||
.build();
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package com.djrapitops.plan.gathering;
|
||||
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@ -64,4 +66,8 @@ public interface ServerSensor<W> {
|
||||
default boolean usingRedisBungee() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default List<PluginMetadata> getInstalledPlugins() {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.gathering.domain;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a plugin that is installed on a server.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class PluginMetadata {
|
||||
|
||||
private final String name;
|
||||
private final String version;
|
||||
|
||||
public PluginMetadata(String name, String version) {
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PluginMetadata that = (PluginMetadata) o;
|
||||
return Objects.equals(getName(), that.getName()) && Objects.equals(getVersion(), that.getVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getName(), getVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PluginMetadata{" +
|
||||
"name='" + name + '\'' +
|
||||
", version='" + version + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.gathering.timed;
|
||||
|
||||
import com.djrapitops.plan.PlanSystem;
|
||||
import com.djrapitops.plan.TaskSystem;
|
||||
import com.djrapitops.plan.gathering.ServerSensor;
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.PluginMetadataQueries;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.PluginVersionTable;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.StorePluginVersionsTransaction;
|
||||
import net.playeranalytics.plugin.scheduling.RunnableFactory;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Gathers information about plugins that have been installed and their version history.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
public class InstalledPluginGatheringTask extends TaskSystem.Task {
|
||||
|
||||
private final ServerSensor<?> serverSensor;
|
||||
private final ServerInfo serverInfo;
|
||||
private final DBSystem dbSystem;
|
||||
|
||||
@Inject
|
||||
public InstalledPluginGatheringTask(ServerSensor<?> serverSensor, ServerInfo serverInfo, DBSystem dbSystem) {
|
||||
this.serverSensor = serverSensor;
|
||||
this.serverInfo = serverInfo;
|
||||
this.dbSystem = dbSystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(RunnableFactory runnableFactory) {
|
||||
runnableFactory.create(this)
|
||||
.runTaskLater(20, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
List<PluginMetadata> installedPlugins = serverSensor.getInstalledPlugins();
|
||||
|
||||
ServerUUID serverUUID = serverInfo.getServerUUID();
|
||||
List<PluginMetadata> previouslyInstalledPlugins = dbSystem.getDatabase()
|
||||
.query(PluginMetadataQueries.getInstalledPlugins(serverUUID));
|
||||
|
||||
List<PluginMetadata> newPlugins = new ArrayList<>();
|
||||
List<PluginMetadata> updatedPlugins = new ArrayList<>();
|
||||
|
||||
Set<String> installedPluginNames = new HashSet<>();
|
||||
for (PluginMetadata installedPlugin : installedPlugins) {
|
||||
installedPluginNames.add(installedPlugin.getName());
|
||||
|
||||
Optional<PluginMetadata> match = previouslyInstalledPlugins.stream()
|
||||
.filter(plugin -> plugin.getName().equals(installedPlugin.getName()))
|
||||
.findFirst();
|
||||
if (match.isEmpty()) {
|
||||
// New plugins are installedPlugins missing from previous list
|
||||
newPlugins.add(installedPlugin);
|
||||
} else {
|
||||
PluginMetadata previousVersion = match.get();
|
||||
String installedVersion = StringUtils.truncate(installedPlugin.getVersion(), PluginVersionTable.MAX_VERSION_LENGTH);
|
||||
if (!installedVersion.equals(previousVersion.getVersion())) {
|
||||
// Updated plugins are plugins in the list with different version
|
||||
updatedPlugins.add(installedPlugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Removed plugins are previously installed plugins missing from installed list
|
||||
List<PluginMetadata> removedPlugins = previouslyInstalledPlugins.stream()
|
||||
.map(PluginMetadata::getName)
|
||||
.filter(pluginName -> !installedPluginNames.contains(pluginName))
|
||||
// Uninstalled plugin version is marked as null
|
||||
.map(pluginName -> new PluginMetadata(pluginName, null))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long enableTime = PlanSystem.getServerEnableTime();
|
||||
List<PluginMetadata> pluginChangeList = new ArrayList<>();
|
||||
pluginChangeList.addAll(newPlugins);
|
||||
pluginChangeList.addAll(updatedPlugins);
|
||||
pluginChangeList.addAll(removedPlugins);
|
||||
|
||||
dbSystem.getDatabase().executeTransaction(new StorePluginVersionsTransaction(enableTime, serverUUID, pluginChangeList));
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import com.djrapitops.plan.settings.config.ConfigNode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Represents a change made to the config structure.
|
||||
@ -103,6 +104,8 @@ public interface ConfigChange {
|
||||
public boolean hasBeenApplied(Config config) {
|
||||
return config.getNode(oldPath)
|
||||
.map(ConfigNode::getString)
|
||||
.map(String::trim)
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.isEmpty()
|
||||
&& config.getNode(newPath).isPresent();
|
||||
}
|
||||
|
@ -270,6 +270,13 @@ public enum HtmlLang implements Lang {
|
||||
LABEL_SERVER_SELECTOR("html.label.serverSelector", "Server selector"),
|
||||
LABEL_APPLY("html.label.apply", "Apply"),
|
||||
LABEL_POSSIBLY_OFFLINE("html.label.serverPossiblyOffline", "Possibly offline"),
|
||||
LABEL_PLUGINS_CURRENTLY_INSTALLED("html.label.currentlyInstalledPlugins", "Currently Installed Plugins"),
|
||||
LABEL_PLUGINS_HISTORY("html.label.pluginHistory", "Plugin History"),
|
||||
LABEL_PLUGINS_VERSION_HISTORY("html.label.pluginVersionHistory", "Plugin Version History"),
|
||||
LABEL_VERSION("html.label.version", "Version"),
|
||||
LABEL_MODIFIED("html.label.modified", "Modified"),
|
||||
LABEL_INSTALLED("html.label.installed", "Installed"),
|
||||
LABEL_UNINSTALLED("html.label.uninstalled", "Uninstalled"),
|
||||
|
||||
LABEL_TABLE_VISIBLE_COLUMNS("html.label.table.visibleColumns", "Visible columns"),
|
||||
LABEL_TABLE_SHOW_N_OF_M("html.label.table.showNofM", "Showing {{n}} of {{m}} entries"),
|
||||
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.storage.database.queries.objects;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.PluginHistoryMetadata;
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.storage.database.queries.Query;
|
||||
import com.djrapitops.plan.storage.database.queries.QueryStatement;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.PluginVersionTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.ServerTable;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
|
||||
|
||||
/**
|
||||
* Queries to the {@link com.djrapitops.plan.storage.database.sql.tables.PluginVersionTable} go here.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class PluginMetadataQueries {
|
||||
|
||||
private PluginMetadataQueries() {
|
||||
/* static method class */
|
||||
}
|
||||
|
||||
public static Query<List<PluginMetadata>> getInstalledPlugins(ServerUUID serverUUID) {
|
||||
@Language("SQL")
|
||||
String sql = SELECT + "*" + FROM + PluginVersionTable.TABLE_NAME +
|
||||
WHERE + PluginVersionTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
||||
ORDER_BY + PluginVersionTable.MODIFIED + " DESC";
|
||||
return new QueryStatement<>(sql, 100) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setString(1, serverUUID.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PluginMetadata> processResults(ResultSet set) throws SQLException {
|
||||
Set<String> foundPlugins = new HashSet<>();
|
||||
List<PluginMetadata> installedPlugins = new ArrayList<>();
|
||||
while (set.next()) {
|
||||
String pluginName = set.getString(PluginVersionTable.PLUGIN_NAME);
|
||||
|
||||
// Only keep the latest information
|
||||
if (foundPlugins.contains(pluginName)) continue;
|
||||
foundPlugins.add(pluginName);
|
||||
|
||||
String version = set.getString(PluginVersionTable.VERSION);
|
||||
if (!set.wasNull()) { // If version is null the plugin is marked as uninstalled.
|
||||
installedPlugins.add(new PluginMetadata(pluginName, version));
|
||||
}
|
||||
}
|
||||
return installedPlugins;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<List<PluginHistoryMetadata>> getPluginHistory(ServerUUID serverUUID) {
|
||||
@Language("SQL")
|
||||
String sql = SELECT + "*" + FROM + PluginVersionTable.TABLE_NAME +
|
||||
WHERE + PluginVersionTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
||||
ORDER_BY + PluginVersionTable.MODIFIED + " DESC, " + PluginVersionTable.PLUGIN_NAME;
|
||||
return db -> db.queryList(sql, PluginMetadataQueries::extractHistoryMetadata, serverUUID);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PluginHistoryMetadata extractHistoryMetadata(ResultSet row) throws SQLException {
|
||||
String name = row.getString(PluginVersionTable.PLUGIN_NAME);
|
||||
String version = row.getString(PluginVersionTable.VERSION);
|
||||
if (row.wasNull()) version = null;
|
||||
long modified = row.getLong(PluginVersionTable.MODIFIED);
|
||||
return new PluginHistoryMetadata(name, version, modified);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.storage.database.sql.tables;
|
||||
|
||||
import com.djrapitops.plan.storage.database.DBType;
|
||||
import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Sql;
|
||||
|
||||
/**
|
||||
* Represents plan_plugin_versions table.
|
||||
* <p>
|
||||
* Keeps track of plugin version history.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class PluginVersionTable {
|
||||
|
||||
public static final String TABLE_NAME = "plan_plugin_versions";
|
||||
|
||||
public static final String ID = "id";
|
||||
public static final String SERVER_ID = "server_id";
|
||||
public static final String PLUGIN_NAME = "plugin_name";
|
||||
public static final String VERSION = "version";
|
||||
public static final String MODIFIED = "modified";
|
||||
|
||||
public static final int MAX_NAME_LENGTH = 100;
|
||||
public static final int MAX_VERSION_LENGTH = 255;
|
||||
|
||||
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " ("
|
||||
+ SERVER_ID + ','
|
||||
+ PLUGIN_NAME + ','
|
||||
+ VERSION + ','
|
||||
+ MODIFIED
|
||||
+ ") VALUES (" + ServerTable.SELECT_SERVER_ID + ", ?, ?, ?)";
|
||||
|
||||
private PluginVersionTable() {
|
||||
/* Static information class */
|
||||
}
|
||||
|
||||
public static String createTableSQL(DBType dbType) {
|
||||
return CreateTableBuilder.create(TABLE_NAME, dbType)
|
||||
.column(ID, Sql.INT).primaryKey()
|
||||
.column(SERVER_ID, Sql.INT).notNull()
|
||||
.column(PLUGIN_NAME, Sql.varchar(100)).notNull()
|
||||
.column(VERSION, Sql.varchar(255))
|
||||
.column(MODIFIED, Sql.LONG).notNull().defaultValue("0")
|
||||
.foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.ID)
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.storage.database.transactions.events;
|
||||
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.PluginVersionTable;
|
||||
import com.djrapitops.plan.storage.database.transactions.ExecBatchStatement;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Stores changes to the plugin list found during enable.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class StorePluginVersionsTransaction extends Transaction {
|
||||
|
||||
private final long time;
|
||||
private final ServerUUID serverUUID;
|
||||
private final List<PluginMetadata> changeList;
|
||||
|
||||
public StorePluginVersionsTransaction(long time, ServerUUID serverUUID, List<PluginMetadata> changeList) {
|
||||
this.time = time;
|
||||
this.serverUUID = serverUUID;
|
||||
this.changeList = changeList;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
execute(new ExecBatchStatement(PluginVersionTable.INSERT_STATEMENT) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
for (PluginMetadata plugin : changeList) {
|
||||
statement.setString(1, serverUUID.toString());
|
||||
statement.setString(2, StringUtils.truncate(plugin.getName(), PluginVersionTable.MAX_NAME_LENGTH));
|
||||
if (plugin.getVersion() == null) {
|
||||
statement.setNull(3, Types.VARCHAR);
|
||||
} else {
|
||||
statement.setString(3, StringUtils.truncate(plugin.getVersion(), PluginVersionTable.MAX_VERSION_LENGTH));
|
||||
}
|
||||
statement.setLong(4, time);
|
||||
statement.addBatch();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
public List<PluginMetadata> getChangeList() {
|
||||
return changeList;
|
||||
}
|
||||
}
|
@ -56,6 +56,7 @@ public class CreateTablesTransaction extends OperationCriticalTransaction {
|
||||
// Ensure plan_security has id column
|
||||
executeOther(new SecurityTableIdPatch());
|
||||
execute(WebUserPreferencesTable.createTableSQL(dbType));
|
||||
execute(PluginVersionTable.createTableSQL(dbType));
|
||||
|
||||
// DataExtension tables
|
||||
execute(ExtensionIconTable.createTableSQL(dbType));
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU 使用率"
|
||||
currentPlayerbase: "当前玩家数"
|
||||
currentUptime: "正常运行时间"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "按天查看"
|
||||
dayOfweek: "星期"
|
||||
deadliestWeapon: "最致命的 PVP 武器"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "信息"
|
||||
insights: "洞察"
|
||||
insights30days: "30 天分析"
|
||||
installed: "Installed"
|
||||
irregular: "偶尔上线"
|
||||
joinAddress: "加入地址"
|
||||
joinAddresses: "加入地址"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "被生物击杀数"
|
||||
mobKdr: "生物 KDR"
|
||||
mobKills: "生物击杀数"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "最常玩的游戏模式"
|
||||
mostPlayedWorld: "玩的最多的世界"
|
||||
name: "名称"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "在线玩家(当前)"
|
||||
playersOnlineOverview: "在线活动总览"
|
||||
playtime: "游玩时间"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "插件"
|
||||
pluginsOverview: "插件总览"
|
||||
punchcard: "打卡"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "趋势"
|
||||
trends30days: "30 天趋势"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "独立玩家"
|
||||
uniquePlayers7days: "独立玩家(7天)"
|
||||
unit:
|
||||
percentage: "百分比"
|
||||
playerCount: "玩家数量"
|
||||
users: "管理用户"
|
||||
version: "Version"
|
||||
veryActive: "非常活跃"
|
||||
weekComparison: "每周对比"
|
||||
weekdays: "'星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU Usage"
|
||||
currentPlayerbase: "Aktuální základna hráčů"
|
||||
currentUptime: "Aktuální doba zapnutí"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Den po dni"
|
||||
dayOfweek: "Den týdne"
|
||||
deadliestWeapon: "Nejsmrtelnější PvP zbraň"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "INFORMACE"
|
||||
insights: "Postřehy"
|
||||
insights30days: "Postřehy za 30 dní"
|
||||
installed: "Installed"
|
||||
irregular: "Nepravidelný"
|
||||
joinAddress: "Adresa Připojení"
|
||||
joinAddresses: "IP Připojení"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Smrti způsobené moby"
|
||||
mobKdr: "Mob KDR"
|
||||
mobKills: "Zabití mobové"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "Nejvíce aktivní mód"
|
||||
mostPlayedWorld: "Nejvíc hraný svět"
|
||||
name: "Jméno"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Hráči online (Nyní)"
|
||||
playersOnlineOverview: "Přehled online aktivity"
|
||||
playtime: "Herní čas"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Pluginy"
|
||||
pluginsOverview: "Přehled pluginů"
|
||||
punchcard: "Štítky"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Trend"
|
||||
trends30days: "Trendy za 30 dní"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Unikátní hráči"
|
||||
uniquePlayers7days: "Unikátních hráčů (7 dní)"
|
||||
unit:
|
||||
percentage: "Procento"
|
||||
playerCount: "Počet hráčů"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Velmi aktivní"
|
||||
weekComparison: "Týdenní srovnání"
|
||||
weekdays: "'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota', 'Neděle'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU Usage"
|
||||
currentPlayerbase: "Aktuelle Spielerbasis"
|
||||
currentUptime: "Current Uptime"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Tag für Tag"
|
||||
dayOfweek: "Tag der Woche"
|
||||
deadliestWeapon: "Tödlichste PvP Waffe"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "INFORMATION"
|
||||
insights: "Insights"
|
||||
insights30days: "Insights for 30 days"
|
||||
installed: "Installed"
|
||||
irregular: "Unregelmäßig"
|
||||
joinAddress: "Join Address"
|
||||
joinAddresses: "Join Addresses"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Tode durch Mobs"
|
||||
mobKdr: "Mob KDR"
|
||||
mobKills: "Mob Kills"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "Meist genutzter Spielmodus"
|
||||
mostPlayedWorld: "Meist gespielte Welt"
|
||||
name: "Name"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Players Online (Now)"
|
||||
playersOnlineOverview: "Online Aktivitätsübersicht"
|
||||
playtime: "Spielzeit"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Plugins"
|
||||
pluginsOverview: "Plugins Overview"
|
||||
punchcard: "Lochkarte"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Trend"
|
||||
trends30days: "Trends für 30 Tage"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Einzigartige Spieler"
|
||||
uniquePlayers7days: "Unique Players (7 days)"
|
||||
unit:
|
||||
percentage: "Percentage"
|
||||
playerCount: "Player Count"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Sehr aktiv"
|
||||
weekComparison: "Wochenvergleich"
|
||||
weekdays: "'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU Usage"
|
||||
currentPlayerbase: "Current Playerbase"
|
||||
currentUptime: "Current Uptime"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Day by Day"
|
||||
dayOfweek: "Day of the Week"
|
||||
deadliestWeapon: "Deadliest PvP Weapon"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "INFORMATION"
|
||||
insights: "Insights"
|
||||
insights30days: "Insights for 30 days"
|
||||
installed: "Installed"
|
||||
irregular: "Irregular"
|
||||
joinAddress: "Join Address"
|
||||
joinAddresses: "Join Addresses"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Mob caused Deaths"
|
||||
mobKdr: "Mob KDR"
|
||||
mobKills: "Mob Kills"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "Most Active Gamemode"
|
||||
mostPlayedWorld: "Most played World"
|
||||
name: "Name"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Players Online (Now)"
|
||||
playersOnlineOverview: "Online Activity Overview"
|
||||
playtime: "Playtime"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Plugins"
|
||||
pluginsOverview: "Plugins Overview"
|
||||
punchcard: "Punchcard"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Trend"
|
||||
trends30days: "Trends for 30 days"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Unique Players"
|
||||
uniquePlayers7days: "Unique Players (7 days)"
|
||||
unit:
|
||||
percentage: "Percentage"
|
||||
playerCount: "Player Count"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Very Active"
|
||||
weekComparison: "Week Comparison"
|
||||
weekdays: "'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU Usage"
|
||||
currentPlayerbase: "base del jugador actual"
|
||||
currentUptime: "Current Uptime"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Día a día"
|
||||
dayOfweek: "Dia de la semana"
|
||||
deadliestWeapon: "Arma PvP más mortal"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "INFORMACIÓN"
|
||||
insights: "Insights"
|
||||
insights30days: "Ideas por 30 días"
|
||||
installed: "Installed"
|
||||
irregular: "Irregular"
|
||||
joinAddress: "Join Address"
|
||||
joinAddresses: "Direcciones de entrada"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Muertes causadas por mobs"
|
||||
mobKdr: "KDR de mobs"
|
||||
mobKills: "Asesinatos de mobs"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "Modo de juego mas activo"
|
||||
mostPlayedWorld: "Mundo más jugado"
|
||||
name: "Nombre"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Players Online (Now)"
|
||||
playersOnlineOverview: "Vista general de la actividad online"
|
||||
playtime: "Tiempo de juego"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Plugins"
|
||||
pluginsOverview: "Plugins Overview"
|
||||
punchcard: "Tarjeta"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Tendencia"
|
||||
trends30days: "Tendencias de 30 días"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Jugadores únicos"
|
||||
uniquePlayers7days: "Unique Players (7 days)"
|
||||
unit:
|
||||
percentage: "Percentage"
|
||||
playerCount: "Player Count"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Muy activo"
|
||||
weekComparison: "Comparación semanal"
|
||||
weekdays: "'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "Suorittimen käyttö"
|
||||
currentPlayerbase: "Nykyiset pelaajat"
|
||||
currentUptime: "Käynnissäoloaika"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Päivittäinen katsaus"
|
||||
dayOfweek: "Viikonpäivä"
|
||||
deadliestWeapon: "Tappavin PvP Ase"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "TIETOJA"
|
||||
insights: "Katsaukset"
|
||||
insights30days: "Katsauksia 30 päivälle"
|
||||
installed: "Installed"
|
||||
irregular: "Epäsäännöllinen"
|
||||
joinAddress: "Liittymisosoite"
|
||||
joinAddresses: "Liittymisosoitteet"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Otusten aiheuttamat Kuolemat"
|
||||
mobKdr: "Otus-Tapposuhde"
|
||||
mobKills: "Tapetut Otukset"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "Aktiivisin pelitila"
|
||||
mostPlayedWorld: "Eniten pelattu maailma"
|
||||
name: "Nimi"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Pelaajia paikalla (Nyt)"
|
||||
playersOnlineOverview: "Yhteenveto Paikallaolosta"
|
||||
playtime: "Peliaika"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Lisäosat"
|
||||
pluginsOverview: "Lisäosien Yhteenveto"
|
||||
punchcard: "Reikäkortti"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Suunta"
|
||||
trends30days: "Suunnat 30 päivälle"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Uniikkeja pelaajia"
|
||||
uniquePlayers7days: "Uniikkeja pelaajia (7 päivää)"
|
||||
unit:
|
||||
percentage: "Prosentti"
|
||||
playerCount: "Pelaajamäärä"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Todella Aktiivinen"
|
||||
weekComparison: "Viikkojen vertaus"
|
||||
weekdays: "'Maanantai', 'Tiistai', 'Keskiviikko', 'Torstai', 'Perjantai', 'Lauantai', 'Sunnuntai'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU Usage"
|
||||
currentPlayerbase: "Base de Joueurs actuelle"
|
||||
currentUptime: "Current Uptime"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Jour par Jour"
|
||||
dayOfweek: "Jour de la Semaine"
|
||||
deadliestWeapon: "1ère Arme de Combat (la plus mortelle)"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "INFORMATIONS"
|
||||
insights: "Insights"
|
||||
insights30days: "Perspectives sur 30 jours"
|
||||
installed: "Installed"
|
||||
irregular: "Irrégulier"
|
||||
joinAddress: "Join Address"
|
||||
joinAddresses: "Adresses de Connexion"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Morts causées par un Mob"
|
||||
mobKdr: "Ratio - Kills / Morts de Mobs -"
|
||||
mobKills: "Kills de Mobs"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "Mode de Jeu le plus utilisé"
|
||||
mostPlayedWorld: "Monde le plus Fréquenté"
|
||||
name: "Nom"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Players Online (Now)"
|
||||
playersOnlineOverview: "Aperçu de l'Activité en Ligne"
|
||||
playtime: "Temps de Jeu"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Plugins"
|
||||
pluginsOverview: "Plugins Overview"
|
||||
punchcard: "Carte Perforée"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Tendances"
|
||||
trends30days: "Tendances sur 30 Jours"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Joueurs Uniques"
|
||||
uniquePlayers7days: "Unique Players (7 days)"
|
||||
unit:
|
||||
percentage: "Percentage"
|
||||
playerCount: "Player Count"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Très Actif"
|
||||
weekComparison: "Comparaison Hebdomadaire"
|
||||
weekdays: "'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU Usage"
|
||||
currentPlayerbase: "Playerbase Corrente"
|
||||
currentUptime: "Current Uptime"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Giorno Per Giorno"
|
||||
dayOfweek: "Giorno della Settimana"
|
||||
deadliestWeapon: "Arma PvP Preferita"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "INFORMAZIONE"
|
||||
insights: "Insights"
|
||||
insights30days: "Grafico in 30 giorni"
|
||||
installed: "Installed"
|
||||
irregular: "Irregolare"
|
||||
joinAddress: "Join Address"
|
||||
joinAddresses: "Join Addresses"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Mob che hanno causato la Morte"
|
||||
mobKdr: "Mob KDR"
|
||||
mobKills: "Mob uccisi"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "La Gamemode più attiva"
|
||||
mostPlayedWorld: "Mondo più giocato"
|
||||
name: "Nome"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Players Online (Now)"
|
||||
playersOnlineOverview: "Panoramica delle attività online"
|
||||
playtime: "Gioco"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Plugins"
|
||||
pluginsOverview: "Plugins Overview"
|
||||
punchcard: "Presenza Settimanale"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Tendenza"
|
||||
trends30days: "Tendenza per 30 giorni"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Giocatori unici"
|
||||
uniquePlayers7days: "Unique Players (7 days)"
|
||||
unit:
|
||||
percentage: "Percentage"
|
||||
playerCount: "Player Count"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Molto Attivo"
|
||||
weekComparison: "Confronto settimanale"
|
||||
weekdays: "'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU使用率"
|
||||
currentPlayerbase: "ログインプレイヤー"
|
||||
currentUptime: "現状のアップタイム"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "詳細情報"
|
||||
dayOfweek: "曜日"
|
||||
deadliestWeapon: "最もPvPで使用されている武器"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "インフォメーション"
|
||||
insights: "Insights"
|
||||
insights30days: "1ヶ月のパンチボード"
|
||||
installed: "Installed"
|
||||
irregular: "たまにログインしている"
|
||||
joinAddress: "参加したサーバーのアドレス"
|
||||
joinAddresses: "参加したサーバーのアドレス"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Mobによって殺された回数"
|
||||
mobKdr: "Mobに対してのKDR"
|
||||
mobKills: "Mobを殺した回数"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "最も使用したゲームモード"
|
||||
mostPlayedWorld: "よくプレイしているワールド"
|
||||
name: "名前"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "オンラインのプレイヤー(今)"
|
||||
playersOnlineOverview: "接続状況の概要"
|
||||
playtime: "プレイ時間"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "プラグイン"
|
||||
pluginsOverview: "プラグイン一覧"
|
||||
punchcard: "パンチカード"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "増減"
|
||||
trends30days: "1ヶ月間の増減"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "接続したプレイヤーの総数"
|
||||
uniquePlayers7days: "直近7日間のユニークプレイヤー数"
|
||||
unit:
|
||||
percentage: "パーセンテージ"
|
||||
playerCount: "プレイヤー数"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "とてもログインしている"
|
||||
weekComparison: "直近1週間での比較"
|
||||
weekdays: "'月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU Usage"
|
||||
currentPlayerbase: "현재 플레이어 베이스"
|
||||
currentUptime: "Current Uptime"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Day by Day"
|
||||
dayOfweek: "요일"
|
||||
deadliestWeapon: "치명적인 PvP 무기"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "정보"
|
||||
insights: "Insights"
|
||||
insights30days: "30일 동안의 인사이트"
|
||||
installed: "Installed"
|
||||
irregular: "불규칙한"
|
||||
joinAddress: "Join Address"
|
||||
joinAddresses: "Join Addresses"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "몬스터한테 죽은 횟수"
|
||||
mobKdr: "몬스터 KDR"
|
||||
mobKills: "몬스터 킬수"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "가장 활동적인 게임모드"
|
||||
mostPlayedWorld: "가장 많이 플레이 한 맵"
|
||||
name: "이름"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Players Online (Now)"
|
||||
playersOnlineOverview: "온라인 활동 개요"
|
||||
playtime: "플레이타임"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "플러그인"
|
||||
pluginsOverview: "Plugins Overview"
|
||||
punchcard: "펀치 카드"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "트렌드"
|
||||
trends30days: "30일 동안의 트렌드"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "기존 플레이어"
|
||||
uniquePlayers7days: "Unique Players (7 days)"
|
||||
unit:
|
||||
percentage: "Percentage"
|
||||
playerCount: "Player Count"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "매우 활성화된"
|
||||
weekComparison: "주 비교"
|
||||
weekdays: "'월요일', '화요일', '수요일', '목요일', '금요일', '토요일', '일요일'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU Usage"
|
||||
currentPlayerbase: "Huidige spelerbasis"
|
||||
currentUptime: "Current Uptime"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Dag voor dag"
|
||||
dayOfweek: "Dag van de Week"
|
||||
deadliestWeapon: "Dodelijkste PvP-wapen"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "INFORMATIE"
|
||||
insights: "Insights"
|
||||
insights30days: "Inzichten voor 30 dagen"
|
||||
installed: "Installed"
|
||||
irregular: "Onregelmatig"
|
||||
joinAddress: "Join Address"
|
||||
joinAddresses: "Inlog adressen"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Mob veroorzaakt doden"
|
||||
mobKdr: "Mob KDR"
|
||||
mobKills: "Mob Moorden"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "Meest actieve spelmodus"
|
||||
mostPlayedWorld: "Meest gespeelde wereld"
|
||||
name: "Naam"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Players Online (Now)"
|
||||
playersOnlineOverview: "Overzicht van online activiteiten"
|
||||
playtime: "Speeltijd"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Plugins"
|
||||
pluginsOverview: "Plugins Overview"
|
||||
punchcard: "Ponskaart"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Trend"
|
||||
trends30days: "Trends voor 30 dagen"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Unieke spelers"
|
||||
uniquePlayers7days: "Unique Players (7 days)"
|
||||
unit:
|
||||
percentage: "Percentage"
|
||||
playerCount: "Player Count"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Heel Actief"
|
||||
weekComparison: "Weekvergelijking"
|
||||
weekdays: "'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU Usage"
|
||||
currentPlayerbase: "Base de Jogadores Atual"
|
||||
currentUptime: "Current Uptime"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Day by Day"
|
||||
dayOfweek: "Day of the Week"
|
||||
deadliestWeapon: "Deadliest PvP Weapon"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "INFORMATION"
|
||||
insights: "Insights"
|
||||
insights30days: "Insights for 30 days"
|
||||
installed: "Installed"
|
||||
irregular: "Irregular"
|
||||
joinAddress: "Join Address"
|
||||
joinAddresses: "Join Addresses"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Mortes causadas por Mobs"
|
||||
mobKdr: "KDR por Mob"
|
||||
mobKills: "Assassinato de Mobs"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "Most Active Gamemode"
|
||||
mostPlayedWorld: "Most played World"
|
||||
name: "Nome"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Players Online (Now)"
|
||||
playersOnlineOverview: "Online Activity Overview"
|
||||
playtime: "Tempo de Jogo"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Plugins"
|
||||
pluginsOverview: "Plugins Overview"
|
||||
punchcard: "Punchcard"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Trend"
|
||||
trends30days: "Trends for 30 days"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Jogadores Únicos"
|
||||
uniquePlayers7days: "Unique Players (7 days)"
|
||||
unit:
|
||||
percentage: "Percentage"
|
||||
playerCount: "Player Count"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Muito Ativo"
|
||||
weekComparison: "Week Comparison"
|
||||
weekdays: "'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "Использование ЦП"
|
||||
currentPlayerbase: "Текущая база игроков"
|
||||
currentUptime: "Время безотказной работы"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Статистика по дням"
|
||||
dayOfweek: "День недели"
|
||||
deadliestWeapon: "Самое смертоносное оружие в PvP"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "ИНФОРМАЦИЯ"
|
||||
insights: "Инсайты"
|
||||
insights30days: "Статистика за 30 дней"
|
||||
installed: "Installed"
|
||||
irregular: "Нерегулярный"
|
||||
joinAddress: "Адрес входа"
|
||||
joinAddresses: "Адресы Входа"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Смерть из-за мобов"
|
||||
mobKdr: "Моб KDR"
|
||||
mobKills: "Убийства мобов"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "Самый активный игровой режим"
|
||||
mostPlayedWorld: "Самый популярный мир"
|
||||
name: "Имя"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Players Online (Now)"
|
||||
playersOnlineOverview: "Обзор сетевой активности"
|
||||
playtime: "Время игры"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Плагины"
|
||||
pluginsOverview: "Обзор плагинов"
|
||||
punchcard: "Перфокарты"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Тенденция"
|
||||
trends30days: "тенденция за 30 дней"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Уникальные игроки"
|
||||
uniquePlayers7days: "Unique Players (7 days)"
|
||||
unit:
|
||||
percentage: "Percentage"
|
||||
playerCount: "Player Count"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Очень активный"
|
||||
weekComparison: "Сравнение за неделю"
|
||||
weekdays: "'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU Usage"
|
||||
currentPlayerbase: "Şuanki Oyuncu Tabanı"
|
||||
currentUptime: "Current Uptime"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Gün gün"
|
||||
dayOfweek: "Day of the Week"
|
||||
deadliestWeapon: "En Ölümcül PvP Silahı"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "DANIŞMA"
|
||||
insights: "Insights"
|
||||
insights30days: "30 günlük bilgiler"
|
||||
installed: "Installed"
|
||||
irregular: "Düzensiz"
|
||||
joinAddress: "Join Address"
|
||||
joinAddresses: "Adreslere Katıl"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Yaratık Yüzünden ölümler"
|
||||
mobKdr: "Mob İstatistiği"
|
||||
mobKills: "Öldürülen Mob"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "En Aktif Oyun Modu"
|
||||
mostPlayedWorld: "En çok oynanan Dünya"
|
||||
name: "İsim"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Players Online (Now)"
|
||||
playersOnlineOverview: "Çevrimiçi Etkinliğe Genel Bakış"
|
||||
playtime: "Oyun Süresi"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Pluginler"
|
||||
pluginsOverview: "Plugins Overview"
|
||||
punchcard: "Punchcard"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Trend"
|
||||
trends30days: "30 günlük trendler"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Sunucuya İlk Defa Girenler"
|
||||
uniquePlayers7days: "Unique Players (7 days)"
|
||||
unit:
|
||||
percentage: "Percentage"
|
||||
playerCount: "Player Count"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Çok Aktif"
|
||||
weekComparison: "Hafta Karşılaştırması"
|
||||
weekdays: "'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "Використання ЦП"
|
||||
currentPlayerbase: "Поточна база гравців"
|
||||
currentUptime: "Час безвідмовної роботи"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "Статистика за днями"
|
||||
dayOfweek: "День тижня"
|
||||
deadliestWeapon: "Найсмертоносніша зброя в PvP"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "Інформація"
|
||||
insights: "Інсайти"
|
||||
insights30days: "Статистика за 30 днів"
|
||||
installed: "Installed"
|
||||
irregular: "Нерегулярний"
|
||||
joinAddress: "Адреса входу"
|
||||
joinAddresses: "Адреси входу"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "Смерть через мобів"
|
||||
mobKdr: "Моб KDR"
|
||||
mobKills: "Вбивства мобів"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "Найактивніший ігровий режим"
|
||||
mostPlayedWorld: "Найпопулярніший світ"
|
||||
name: "Ім'я"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "Гравці онлайн (зараз)"
|
||||
playersOnlineOverview: "Огляд мережевої активності"
|
||||
playtime: "Час гри"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "Плагіни"
|
||||
pluginsOverview: "Огляд плагінів"
|
||||
punchcard: "Перфокарти"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "Тенденція"
|
||||
trends30days: "Тенденція за 30 днів"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "Унікальні гравці"
|
||||
uniquePlayers7days: "Унікальні гравці (7 днів)"
|
||||
unit:
|
||||
percentage: "Відсоток"
|
||||
playerCount: "Кількість гравців"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "Дуже активний"
|
||||
weekComparison: "Порівняння за тиждень"
|
||||
weekdays: "'Понеділок', 'Вівторок', 'Середа', 'Четвер', 'П`ятниця', 'Субота', 'Неділя'"
|
||||
|
@ -325,6 +325,7 @@ html:
|
||||
cpuUsage: "CPU 使用率"
|
||||
currentPlayerbase: "目前玩家數量"
|
||||
currentUptime: "Current Uptime"
|
||||
currentlyInstalledPlugins: "Currently Installed Plugins"
|
||||
dayByDay: "按天查看"
|
||||
dayOfweek: "星期"
|
||||
deadliestWeapon: "最致命的 PvP 武器"
|
||||
@ -418,6 +419,7 @@ html:
|
||||
information: "訊息"
|
||||
insights: "Insights"
|
||||
insights30days: "30 天分析"
|
||||
installed: "Installed"
|
||||
irregular: "偶爾上線"
|
||||
joinAddress: "Join Address"
|
||||
joinAddresses: "加入位址"
|
||||
@ -469,6 +471,7 @@ html:
|
||||
mobDeaths: "被生物擊殺數"
|
||||
mobKdr: "生物 KDR"
|
||||
mobKills: "生物擊殺數"
|
||||
modified: "Modified"
|
||||
mostActiveGamemode: "最常玩的遊戲模式"
|
||||
mostPlayedWorld: "玩的最多的世界"
|
||||
name: "名稱"
|
||||
@ -512,6 +515,8 @@ html:
|
||||
playersOnlineNow: "線上玩家 (Now)"
|
||||
playersOnlineOverview: "線上活動概覽"
|
||||
playtime: "遊玩時間"
|
||||
pluginHistory: "Plugin History"
|
||||
pluginVersionHistory: "Plugin Version History"
|
||||
plugins: "插件"
|
||||
pluginsOverview: "插件概覽"
|
||||
punchcard: "打卡"
|
||||
@ -599,12 +604,14 @@ html:
|
||||
tps: "TPS"
|
||||
trend: "趨勢"
|
||||
trends30days: "30 天趨勢"
|
||||
uninstalled: "Uninstalled"
|
||||
uniquePlayers: "獨立玩家"
|
||||
uniquePlayers7days: "獨立玩家 (7 days)"
|
||||
unit:
|
||||
percentage: "Percentage"
|
||||
playerCount: "Player Count"
|
||||
users: "Manage Users"
|
||||
version: "Version"
|
||||
veryActive: "非常活躍"
|
||||
weekComparison: "每週對比"
|
||||
weekdays: "'星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'"
|
||||
|
@ -155,7 +155,9 @@ class AccessControlTest {
|
||||
Arguments.of("/v1/deleteGroup?group=admin&moveTo=no_access", WebPermission.MANAGE_GROUPS, 400, 403),
|
||||
Arguments.of("/v1/saveGroupPermissions?group=admin", WebPermission.MANAGE_GROUPS, 400, 403),
|
||||
Arguments.of("/v1/preferences", WebPermission.ACCESS, 200, 200),
|
||||
Arguments.of("/v1/storePreferences", WebPermission.ACCESS, 400, 400)
|
||||
Arguments.of("/v1/storePreferences", WebPermission.ACCESS, 400, 400),
|
||||
Arguments.of("/v1/pluginHistory?server=" + TestConstants.SERVER_UUID_STRING, WebPermission.PAGE_NETWORK_PLUGIN_HISTORY, 200, 403),
|
||||
Arguments.of("/v1/pluginHistory?server=" + TestConstants.SERVER_UUID_STRING, WebPermission.PAGE_SERVER_PLUGIN_HISTORY, 200, 403)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -146,6 +146,7 @@ class AccessControlVisibilityTest {
|
||||
Arguments.arguments(WebPermission.PAGE_SERVER_PERFORMANCE_GRAPHS, "performance-graphs", "performance"),
|
||||
Arguments.arguments(WebPermission.PAGE_SERVER_PERFORMANCE_OVERVIEW, "performance-as-numbers", "performance"),
|
||||
Arguments.arguments(WebPermission.PAGE_SERVER_PERFORMANCE_OVERVIEW, "performance-insights", "performance"),
|
||||
Arguments.arguments(WebPermission.PAGE_SERVER_PLUGIN_HISTORY, "server-plugin-history", "plugin-history"),
|
||||
Arguments.arguments(WebPermission.PAGE_SERVER_PLUGINS, "server-plugin-data", "plugins-overview")
|
||||
);
|
||||
}
|
||||
@ -174,6 +175,7 @@ class AccessControlVisibilityTest {
|
||||
Arguments.arguments(WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP, "geolocations", "geolocations"),
|
||||
Arguments.arguments(WebPermission.PAGE_NETWORK_GEOLOCATIONS_PING_PER_COUNTRY, "ping-per-country", "geolocations"),
|
||||
Arguments.arguments(WebPermission.PAGE_NETWORK_PERFORMANCE, "row-network-performance-0", "performance"),
|
||||
Arguments.arguments(WebPermission.PAGE_NETWORK_PLUGIN_HISTORY, "network-plugin-history", "plugin-history"),
|
||||
Arguments.arguments(WebPermission.PAGE_NETWORK_PLUGINS, "server-plugin-data", "plugins-overview")
|
||||
);
|
||||
}
|
||||
@ -221,7 +223,7 @@ class AccessControlVisibilityTest {
|
||||
}
|
||||
|
||||
@DisplayName("Whole page is not visible with permission")
|
||||
@ParameterizedTest(name = "Access with no visibility (needs {0}) can't see element #{1} in /{2}")
|
||||
@ParameterizedTest(name = "Access with no visibility needs {0} can't see element #{1} in /{2}")
|
||||
@MethodSource("pageLevelVisibleCases")
|
||||
void pageNotVisible(WebPermission permission, String element, String page, Database database, ChromeDriver driver) throws Exception {
|
||||
User user = registerUser(database);
|
||||
@ -272,7 +274,7 @@ class AccessControlVisibilityTest {
|
||||
}
|
||||
|
||||
@DisplayName("Server element is not visible without permission")
|
||||
@ParameterizedTest(name = "Access to server page with no visibility (needs {0}) can't see element #{1} in section /server/uuid/{2}")
|
||||
@ParameterizedTest(name = "Access to server page with no visibility needs {0} can't see element #{1} in section /server/uuid/{2}")
|
||||
@MethodSource("serverPageElementVisibleCases")
|
||||
void serverPageElementNotVisible(WebPermission permission, String element, String section, Database database, ServerUUID serverUUID, ChromeDriver driver) throws Exception {
|
||||
User user = registerUser(database, WebPermission.ACCESS_SERVER);
|
||||
@ -310,7 +312,7 @@ class AccessControlVisibilityTest {
|
||||
}
|
||||
|
||||
@DisplayName("Network element is not visible without permission")
|
||||
@ParameterizedTest(name = "Access to network page with no visibility (needs {0}) can't see element #{1} in section /network/{2}")
|
||||
@ParameterizedTest(name = "Access to network page with no visibility needs {0} can't see element #{1} in section /network/{2}")
|
||||
@MethodSource("networkPageElementVisibleCases")
|
||||
void networkPageElementNotVisible(WebPermission permission, String element, String section, Database database, ChromeDriver driver) throws Exception {
|
||||
User user = registerUser(database, WebPermission.ACCESS_NETWORK);
|
||||
@ -343,7 +345,7 @@ class AccessControlVisibilityTest {
|
||||
}
|
||||
|
||||
@DisplayName("Player element is not visible without permission")
|
||||
@ParameterizedTest(name = "Access to player page with no visibility (needs {0}) can't see element #{1} in section /player/uuid/{2}")
|
||||
@ParameterizedTest(name = "Access to player page with no visibility needs {0} can't see element #{1} in section /player/uuid/{2}")
|
||||
@MethodSource("playerPageVisibleCases")
|
||||
void playerPageElementNotVisible(WebPermission permission, String element, String section, Database database, ServerUUID serverUUID, ChromeDriver driver) throws Exception {
|
||||
User user = registerUser(database, WebPermission.ACCESS_PLAYER);
|
||||
|
@ -91,6 +91,7 @@ class HttpAccessControlTest {
|
||||
"/v1/saveGroupPermissions",
|
||||
"/v1/deleteGroup",
|
||||
"/v1/storePreferences",
|
||||
"/v1/pluginHistory?server=" + TestConstants.SERVER_UUID_STRING,
|
||||
"/manage",
|
||||
"/auth/register",
|
||||
"/auth/login",
|
||||
|
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.gathering.timed;
|
||||
|
||||
import com.djrapitops.plan.gathering.ServerSensor;
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.StorePluginVersionsTransaction;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import utilities.TestConstants;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests to ensure that plugin gathering works as intended.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class InstalledPluginGatheringTaskTest {
|
||||
|
||||
@Mock
|
||||
ServerSensor<?> serverSensor;
|
||||
@Mock
|
||||
ServerInfo serverInfo;
|
||||
@Mock
|
||||
DBSystem dbSystem;
|
||||
@Mock
|
||||
Database database;
|
||||
|
||||
InstalledPluginGatheringTask underTest;
|
||||
|
||||
Transaction capturedTransaction;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
when(database.executeTransaction(any())).then(invocation -> {
|
||||
capturedTransaction = invocation.getArgument(0);
|
||||
return CompletableFuture.allOf();
|
||||
});
|
||||
when(dbSystem.getDatabase()).thenReturn(database);
|
||||
when(serverInfo.getServerUUID()).thenReturn(TestConstants.SERVER_UUID);
|
||||
underTest = new InstalledPluginGatheringTask(serverSensor, serverInfo, dbSystem);
|
||||
}
|
||||
|
||||
@Test
|
||||
void newPluginsAreIncluded() {
|
||||
List<PluginMetadata> previouslyInstalledPlugins = List.of();
|
||||
List<PluginMetadata> installedPlugins = List.of(
|
||||
new PluginMetadata("Plan", "5.6 build 2121"),
|
||||
new PluginMetadata("LittleChef", "1.0.2")
|
||||
);
|
||||
when(database.query(any())).thenReturn(previouslyInstalledPlugins);
|
||||
when(serverSensor.getInstalledPlugins()).thenReturn(installedPlugins);
|
||||
|
||||
underTest.run();
|
||||
|
||||
List<PluginMetadata> changeList = ((StorePluginVersionsTransaction) capturedTransaction).getChangeList();
|
||||
assertEquals(installedPlugins, changeList);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void onlyUpdatedPluginsAreIncluded() {
|
||||
List<PluginMetadata> previouslyInstalledPlugins = List.of(
|
||||
new PluginMetadata("Plan", "5.6 build 2121"),
|
||||
new PluginMetadata("LittleChef", "1.0.2")
|
||||
);
|
||||
List<PluginMetadata> installedPlugins = List.of(
|
||||
new PluginMetadata("Plan", "5.6 build 2121"),
|
||||
new PluginMetadata("LittleChef", "1.0.3")
|
||||
);
|
||||
when(database.query(any())).thenReturn(previouslyInstalledPlugins);
|
||||
when(serverSensor.getInstalledPlugins()).thenReturn(installedPlugins);
|
||||
|
||||
underTest.run();
|
||||
|
||||
List<PluginMetadata> expected = List.of(
|
||||
new PluginMetadata("LittleChef", "1.0.3")
|
||||
);
|
||||
List<PluginMetadata> changeList = ((StorePluginVersionsTransaction) capturedTransaction).getChangeList();
|
||||
assertEquals(expected, changeList);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void removedPluginsAreIncluded() {
|
||||
List<PluginMetadata> previouslyInstalledPlugins = List.of(
|
||||
new PluginMetadata("Plan", "5.6 build 2121"),
|
||||
new PluginMetadata("LittleChef", "1.0.2")
|
||||
);
|
||||
List<PluginMetadata> installedPlugins = List.of(
|
||||
new PluginMetadata("Plan", "5.6 build 2121")
|
||||
);
|
||||
when(database.query(any())).thenReturn(previouslyInstalledPlugins);
|
||||
when(serverSensor.getInstalledPlugins()).thenReturn(installedPlugins);
|
||||
|
||||
underTest.run();
|
||||
|
||||
List<PluginMetadata> expected = List.of(
|
||||
new PluginMetadata("LittleChef", null)
|
||||
);
|
||||
List<PluginMetadata> changeList = ((StorePluginVersionsTransaction) capturedTransaction).getChangeList();
|
||||
assertEquals(expected, changeList);
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionQue
|
||||
import com.djrapitops.plan.storage.database.queries.*;
|
||||
import com.djrapitops.plan.storage.database.queries.analysis.PlayerRetentionQueriesTest;
|
||||
import com.djrapitops.plan.storage.database.queries.analysis.TopListQueriesTest;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.PluginMetadataQueriesTest;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.ChangeUserUUIDTransactionTest;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.CombineUserTransactionTest;
|
||||
import com.djrapitops.plan.storage.database.transactions.patches.AfterBadJoinAddressDataCorrectionPatchTest;
|
||||
@ -45,6 +46,7 @@ public interface DatabaseTestAggregate extends
|
||||
ExtensionQueryResultTableDataQueryTest,
|
||||
BadJoinAddressDataCorrectionPatchTest,
|
||||
AfterBadJoinAddressDataCorrectionPatchTest,
|
||||
PlayerRetentionQueriesTest {
|
||||
PlayerRetentionQueriesTest,
|
||||
PluginMetadataQueriesTest {
|
||||
/* Collects all query tests together so its easier to implement database tests */
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.storage.database.queries.objects;
|
||||
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
import com.djrapitops.plan.storage.database.DatabaseTestPreparer;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.StorePluginVersionsTransaction;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests for {@link PluginMetadataQueries}.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public interface PluginMetadataQueriesTest extends DatabaseTestPreparer {
|
||||
|
||||
@Test
|
||||
@DisplayName("Plugin Metadata is stored")
|
||||
default void pluginMetadataIsStored() {
|
||||
List<PluginMetadata> changeSet = List.of(
|
||||
new PluginMetadata("Plan", "5.6 build 2121"),
|
||||
new PluginMetadata("LittleChef", "1.0.2"),
|
||||
new PluginMetadata("LittleFX", null)
|
||||
);
|
||||
db().executeTransaction(new StorePluginVersionsTransaction(System.currentTimeMillis(), serverUUID(), changeSet));
|
||||
|
||||
List<PluginMetadata> expected = List.of(
|
||||
new PluginMetadata("Plan", "5.6 build 2121"),
|
||||
new PluginMetadata("LittleChef", "1.0.2")
|
||||
);
|
||||
List<PluginMetadata> result = db().query(PluginMetadataQueries.getInstalledPlugins(serverUUID()));
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
}
|
@ -17,6 +17,9 @@
|
||||
package net.playeranalytics.plan.gathering;
|
||||
|
||||
import com.djrapitops.plan.gathering.ServerSensor;
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import net.fabricmc.loader.impl.FabricLoaderImpl;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
@ -25,6 +28,7 @@ import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Singleton
|
||||
@ -89,4 +93,14 @@ public class FabricSensor implements ServerSensor<ServerWorld> {
|
||||
public List<String> getOnlinePlayerNames() {
|
||||
return Arrays.asList(server.getPlayerNames());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PluginMetadata> getInstalledPlugins() {
|
||||
return FabricLoaderImpl.INSTANCE.getMods().stream()
|
||||
.map(ModContainer::getMetadata)
|
||||
.map(metadata -> new PluginMetadata(
|
||||
Optional.ofNullable(metadata.getName()).orElse(metadata.getId()),
|
||||
metadata.getVersion().getFriendlyString()))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import com.djrapitops.plan.delivery.webserver.configuration.AddressAllowList;
|
||||
import com.djrapitops.plan.extension.ExtensionServerDataUpdater;
|
||||
import com.djrapitops.plan.gathering.ShutdownDataPreservation;
|
||||
import com.djrapitops.plan.gathering.ShutdownHook;
|
||||
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;
|
||||
@ -98,4 +99,8 @@ public interface FabricTaskModule {
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindAddressAllowListUpdateTask(AddressAllowList addressAllowList);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindInstalledPluginGatheringTask(InstalledPluginGatheringTask installedPluginGatheringTask);
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ package com.djrapitops.plan.gathering;
|
||||
|
||||
import cn.nukkit.Player;
|
||||
import cn.nukkit.level.Level;
|
||||
import cn.nukkit.plugin.Plugin;
|
||||
import com.djrapitops.plan.PlanNukkit;
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@ -74,4 +76,13 @@ public class NukkitSensor implements ServerSensor<Level> {
|
||||
.map(Player::getName)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PluginMetadata> getInstalledPlugins() {
|
||||
return plugin.getServer().getPluginManager()
|
||||
.getPlugins().values().stream()
|
||||
.map(Plugin::getDescription)
|
||||
.map(description -> new PluginMetadata(description.getName(), description.getVersion()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import com.djrapitops.plan.delivery.webserver.configuration.AddressAllowList;
|
||||
import com.djrapitops.plan.extension.ExtensionServerDataUpdater;
|
||||
import com.djrapitops.plan.gathering.ShutdownDataPreservation;
|
||||
import com.djrapitops.plan.gathering.ShutdownHook;
|
||||
import com.djrapitops.plan.gathering.timed.InstalledPluginGatheringTask;
|
||||
import com.djrapitops.plan.gathering.timed.NukkitPingCounter;
|
||||
import com.djrapitops.plan.gathering.timed.ServerTPSCounter;
|
||||
import com.djrapitops.plan.gathering.timed.SystemUsageBuffer;
|
||||
@ -98,4 +99,8 @@ public interface NukkitTaskModule {
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindAddressAllowListUpdateTask(AddressAllowList addressAllowList);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindInstalledPluginGatheringTask(InstalledPluginGatheringTask installedPluginGatheringTask);
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ const PlayerbaseOverview = React.lazy(() => import("./views/server/PlayerbaseOve
|
||||
const ServerPlayers = React.lazy(() => import("./views/server/ServerPlayers"));
|
||||
const ServerGeolocations = React.lazy(() => import("./views/server/ServerGeolocations"));
|
||||
const ServerPerformance = React.lazy(() => import("./views/server/ServerPerformance"));
|
||||
const ServerPluginHistory = React.lazy(() => import('./views/server/ServerPluginHistory'));
|
||||
const ServerPluginData = React.lazy(() => import("./views/server/ServerPluginData"));
|
||||
const ServerWidePluginData = React.lazy(() => import("./views/server/ServerWidePluginData"));
|
||||
const ServerJoinAddresses = React.lazy(() => import("./views/server/ServerJoinAddresses"));
|
||||
@ -50,6 +51,7 @@ const NetworkPlayerRetention = React.lazy(() => import("./views/network/NetworkP
|
||||
const NetworkGeolocations = React.lazy(() => import("./views/network/NetworkGeolocations"));
|
||||
const NetworkPlayerbaseOverview = React.lazy(() => import("./views/network/NetworkPlayerbaseOverview"));
|
||||
const NetworkPerformance = React.lazy(() => import("./views/network/NetworkPerformance"));
|
||||
const NetworkPluginHistory = React.lazy(() => import('./views/network/NetworkPluginHistory'));
|
||||
|
||||
const PlayersPage = React.lazy(() => import("./views/layout/PlayersPage"));
|
||||
const AllPlayers = React.lazy(() => import("./views/players/AllPlayers"));
|
||||
@ -163,6 +165,7 @@ function App() {
|
||||
<Route path="players" element={<Lazy><ServerPlayers/></Lazy>}/>
|
||||
<Route path="geolocations" element={<Lazy><ServerGeolocations/></Lazy>}/>
|
||||
<Route path="performance" element={<Lazy><ServerPerformance/></Lazy>}/>
|
||||
<Route path="plugin-history" element={<Lazy><ServerPluginHistory/></Lazy>}/>
|
||||
<Route path="plugins-overview" element={<Lazy><ServerPluginData/></Lazy>}/>
|
||||
<Route path="plugins/:plugin" element={<Lazy><ServerWidePluginData/></Lazy>}/>
|
||||
<Route path="*" element={<ErrorView error={{
|
||||
@ -183,6 +186,7 @@ function App() {
|
||||
<Route path="join-addresses" element={<Lazy><NetworkJoinAddresses/></Lazy>}/>
|
||||
<Route path="players" element={<Lazy><AllPlayers/></Lazy>}/>
|
||||
<Route path="geolocations" element={<Lazy><NetworkGeolocations/></Lazy>}/>
|
||||
<Route path="plugin-history" element={<Lazy><NetworkPluginHistory/></Lazy>}/>
|
||||
<Route path="plugins-overview" element={<Lazy><ServerPluginData/></Lazy>}/>
|
||||
<Route path="plugins/:plugin" element={<Lazy><ServerWidePluginData/></Lazy>}/>
|
||||
<Route path="*" element={<ErrorView error={{
|
||||
|
@ -2,11 +2,12 @@ import React, {useEffect, useState} from "react";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
||||
|
||||
const TabButton = ({id, name, href, icon, color, active}) => {
|
||||
const TabButton = ({id, name, href, icon, color, active, disabled}) => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<li className="nav-item" id={id}>
|
||||
<button className={"nav-link col-black" + (active ? ' active' : '')} aria-selected={active} role="tab"
|
||||
<li className={"nav-item" + (disabled ? ' disabled' : '')} id={id}>
|
||||
<button disabled={disabled} className={"nav-link col-black" + (active ? ' active' : '')}
|
||||
aria-selected={active} role="tab"
|
||||
onClick={() => navigate('#' + href, {replace: true})}>
|
||||
<Fa icon={icon} className={'col-' + color}/> {name}
|
||||
</button>
|
||||
@ -26,6 +27,7 @@ const TabButtons = ({tabs, selectedTab}) => {
|
||||
icon={tab.icon}
|
||||
color={tab.color}
|
||||
active={tab.href === selectedTab}
|
||||
disabled={tab.disabled}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import CardHeader from "../CardHeader";
|
||||
import {faCube, faCubes, faSignal} from "@fortawesome/free-solid-svg-icons";
|
||||
import {Card} from "react-bootstrap";
|
||||
import {ErrorViewCard} from "../../../views/ErrorView";
|
||||
import FormattedDate from "../../text/FormattedDate";
|
||||
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
||||
import {faCalendar} from "@fortawesome/free-regular-svg-icons";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {CardLoader} from "../../navigation/Loader";
|
||||
import DataTablesTable from "../../table/DataTablesTable";
|
||||
|
||||
const PluginCurrentCard = ({data, loadingError}) => {
|
||||
const {t} = useTranslation();
|
||||
|
||||
if (loadingError) return <ErrorViewCard error={loadingError}/>;
|
||||
if (!data) return <CardLoader/>;
|
||||
|
||||
const history = [];
|
||||
|
||||
for (const entry of data.history) {
|
||||
if (history.find(e => e.name === entry.name)) continue;
|
||||
history.push(entry);
|
||||
}
|
||||
|
||||
const table = {
|
||||
columns: [{
|
||||
title: <><Fa icon={faCube}/> {t('html.label.name')}</>,
|
||||
data: "name"
|
||||
}, {
|
||||
title: <><Fa icon={faSignal}/> {t('html.label.version')}</>,
|
||||
data: "version"
|
||||
}, {
|
||||
title: <><Fa icon={faCalendar}/> {t('html.label.modified')}</>,
|
||||
data: {_: "modified", display: "modifiedDisplay"}
|
||||
}],
|
||||
data: history.length ? history.filter(entry => entry.version)
|
||||
.map(entry => {
|
||||
return {
|
||||
name: entry.name,
|
||||
version: t(entry.version),
|
||||
modified: entry.modified,
|
||||
modifiedDisplay: <FormattedDate date={entry.modified}/>
|
||||
}
|
||||
}) : [{name: t('generic.noData'), version: '', 'modified': 0, modifiedDisplay: ''}]
|
||||
};
|
||||
const options = {
|
||||
responsive: true,
|
||||
deferRender: true,
|
||||
columns: table.columns,
|
||||
data: table.data,
|
||||
pagingType: "numbers",
|
||||
order: [[0, "desc"]]
|
||||
}
|
||||
|
||||
const rowKeyFunction = (row, column) => {
|
||||
return row.name + "-" + row.version + '-' + row.modified + '-' + JSON.stringify(column?.data);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader icon={faCubes} label={'html.label.currentlyInstalledPlugins'} color={"indigo"}/>
|
||||
<DataTablesTable id={"plugin-current"} options={options} rowKeyFunction={rowKeyFunction}/>
|
||||
</Card>
|
||||
)
|
||||
};
|
||||
|
||||
export default PluginCurrentCard
|
@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import CardHeader from "../CardHeader";
|
||||
import {faCodeCompare, faCube, faSignal} from "@fortawesome/free-solid-svg-icons";
|
||||
import {Card} from "react-bootstrap";
|
||||
import {ErrorViewCard} from "../../../views/ErrorView";
|
||||
import FormattedDate from "../../text/FormattedDate";
|
||||
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
|
||||
import {faCalendar} from "@fortawesome/free-regular-svg-icons";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {CardLoader} from "../../navigation/Loader";
|
||||
import DataTablesTable from "../../table/DataTablesTable";
|
||||
|
||||
const PluginHistoryCard = ({data, loadingError}) => {
|
||||
const {t} = useTranslation();
|
||||
|
||||
if (loadingError) return <ErrorViewCard error={loadingError}/>;
|
||||
if (!data) return <CardLoader/>;
|
||||
|
||||
const history = data.history;
|
||||
|
||||
const table = {
|
||||
columns: [{
|
||||
title: <><Fa icon={faCube}/> {t('html.label.name')}</>,
|
||||
data: "name"
|
||||
}, {
|
||||
title: <><Fa icon={faSignal}/> {t('html.label.version')}</>,
|
||||
data: "version"
|
||||
}, {
|
||||
title: <><Fa icon={faCalendar}/> {t('html.label.modified')}</>,
|
||||
data: {_: "modified", display: "modifiedDisplay"}
|
||||
}],
|
||||
data: history.length ? history.map(entry => {
|
||||
return {
|
||||
name: entry.name,
|
||||
version: t(entry.version || 'html.label.uninstalled'),
|
||||
modified: entry.modified,
|
||||
modifiedDisplay: <FormattedDate date={entry.modified}/>
|
||||
}
|
||||
}) : [{name: t('generic.noData'), version: '', 'modified': 0, modifiedDisplay: ''}]
|
||||
};
|
||||
const options = {
|
||||
responsive: true,
|
||||
deferRender: true,
|
||||
columns: table.columns,
|
||||
data: table.data,
|
||||
pagingType: "numbers",
|
||||
order: [[2, "desc"]]
|
||||
}
|
||||
|
||||
const rowKeyFunction = (row, column) => {
|
||||
return row.name + "-" + row.version + '-' + row.modified + '-' + JSON.stringify(column?.data);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader icon={faCodeCompare} label={'html.label.pluginVersionHistory'} color={"indigo"}/>
|
||||
<DataTablesTable id={"plugin-history"} options={options} rowKeyFunction={rowKeyFunction}/>
|
||||
</Card>
|
||||
)
|
||||
};
|
||||
|
||||
export default PluginHistoryCard
|
@ -153,13 +153,16 @@ const PerformanceGraphsCard = ({data}) => {
|
||||
setPerformanceSeries(series);
|
||||
}, [data, setPerformanceSeries])
|
||||
|
||||
const dataIncludesGameServers = useMemo(() => data.servers && Boolean(data.servers.filter(server => !server.proxy).length), [data]);
|
||||
|
||||
const tabs = useMemo(() => [
|
||||
{
|
||||
name: t('html.label.playersOnline'), icon: faUser, color: 'light-blue', href: 'players-online',
|
||||
element: <Tab data={performanceSeries.players} yAxis={yAxisConfigurations.PLAYERS_ONLINE}/>
|
||||
}, {
|
||||
name: t('html.label.tps'), icon: faTachometerAlt, color: 'red', href: 'tps',
|
||||
element: <Tab data={performanceSeries.tps} yAxis={yAxisConfigurations.TPS}/>
|
||||
element: <Tab data={performanceSeries.tps} yAxis={yAxisConfigurations.TPS}/>,
|
||||
disabled: !dataIncludesGameServers
|
||||
}, {
|
||||
name: t('html.label.cpu'), icon: faTachometerAlt, color: 'amber', href: 'cpu',
|
||||
element: <Tab data={performanceSeries.cpu} yAxis={yAxisConfigurations.CPU}/>
|
||||
@ -168,10 +171,12 @@ const PerformanceGraphsCard = ({data}) => {
|
||||
element: <Tab data={performanceSeries.ram} yAxis={yAxisConfigurations.RAM_OR_DISK}/>
|
||||
}, {
|
||||
name: t('html.label.entities'), icon: faDragon, color: 'purple', href: 'entities',
|
||||
element: <Tab data={performanceSeries.entities} yAxis={yAxisConfigurations.ENTITIES}/>
|
||||
element: <Tab data={performanceSeries.entities} yAxis={yAxisConfigurations.ENTITIES}/>,
|
||||
disabled: !dataIncludesGameServers
|
||||
}, {
|
||||
name: t('html.label.loadedChunks'), icon: faMap, color: 'blue-grey', href: 'chunks',
|
||||
element: <Tab data={performanceSeries.chunks} yAxis={yAxisConfigurations.CHUNKS}/>
|
||||
element: <Tab data={performanceSeries.chunks} yAxis={yAxisConfigurations.CHUNKS}/>,
|
||||
disabled: !dataIncludesGameServers
|
||||
}, {
|
||||
name: t('html.label.diskSpace'), icon: faHdd, color: 'green', href: 'disk',
|
||||
element: <Tab data={performanceSeries.disk} yAxis={yAxisConfigurations.RAM_OR_DISK}/>
|
||||
@ -180,7 +185,7 @@ const PerformanceGraphsCard = ({data}) => {
|
||||
element: networkMetadata ? <PingTab identifier={networkMetadata.currentServer.serverUUID}/> :
|
||||
<ChartLoader/>
|
||||
},
|
||||
], [performanceSeries, networkMetadata, t]);
|
||||
], [performanceSeries, networkMetadata, t, dataIncludesGameServers]);
|
||||
|
||||
if (!data || !Object.values(data).length) return <CardLoader/>
|
||||
if (data.errors.length) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {useParams} from "react-router-dom";
|
||||
import {useDataRequest} from "../../../../hooks/dataFetchHook";
|
||||
import {fetchOptimizedPerformance, fetchPingGraph} from "../../../../service/serverService";
|
||||
import {fetchOptimizedPerformance, fetchPingGraph, fetchPluginHistory} from "../../../../service/serverService";
|
||||
import {ErrorViewBody} from "../../../../views/ErrorView";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Card} from "react-bootstrap";
|
||||
@ -15,40 +15,46 @@ import WorldPerformanceGraph from "../../../graphs/performance/WorldPerformanceG
|
||||
import DiskPerformanceGraph from "../../../graphs/performance/DiskPerformanceGraph";
|
||||
import PingGraph from "../../../graphs/performance/PingGraph";
|
||||
import {mapPerformanceDataToSeries} from "../../../../util/graphs";
|
||||
import {useAuth} from "../../../../hooks/authenticationHook";
|
||||
|
||||
const AllGraphTab = ({data, dataSeries, loadingError}) => {
|
||||
const AllGraphTab = ({data, dataSeries, pluginHistorySeries, loadingError}) => {
|
||||
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||
if (!dataSeries) return <ChartLoader style={{height: "450px"}}/>;
|
||||
|
||||
return <AllPerformanceGraph id="server-performance-all-chart" data={data} dataSeries={dataSeries}/>
|
||||
return <AllPerformanceGraph id="server-performance-all-chart" data={data} dataSeries={dataSeries}
|
||||
pluginHistorySeries={pluginHistorySeries}/>
|
||||
}
|
||||
|
||||
const TpsGraphTab = ({data, dataSeries, loadingError}) => {
|
||||
const TpsGraphTab = ({data, dataSeries, pluginHistorySeries, loadingError}) => {
|
||||
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||
if (!dataSeries) return <ChartLoader style={{height: "450px"}}/>;
|
||||
|
||||
return <TpsPerformanceGraph id="server-performance-tps-chart" data={data} dataSeries={dataSeries}/>
|
||||
return <TpsPerformanceGraph id="server-performance-tps-chart" data={data} dataSeries={dataSeries}
|
||||
pluginHistorySeries={pluginHistorySeries}/>
|
||||
}
|
||||
|
||||
const CpuRamGraphTab = ({data, dataSeries, loadingError}) => {
|
||||
const CpuRamGraphTab = ({data, dataSeries, pluginHistorySeries, loadingError}) => {
|
||||
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||
if (!dataSeries) return <ChartLoader style={{height: "450px"}}/>;
|
||||
|
||||
return <CpuRamPerformanceGraph id="server-performance-cpuram-chart" data={data} dataSeries={dataSeries}/>
|
||||
return <CpuRamPerformanceGraph id="server-performance-cpuram-chart" data={data} dataSeries={dataSeries}
|
||||
pluginHistorySeries={pluginHistorySeries}/>
|
||||
}
|
||||
|
||||
const WorldGraphTab = ({data, dataSeries, loadingError}) => {
|
||||
const WorldGraphTab = ({data, dataSeries, pluginHistorySeries, loadingError}) => {
|
||||
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||
if (!dataSeries) return <ChartLoader style={{height: "450px"}}/>;
|
||||
|
||||
return <WorldPerformanceGraph id="server-performance-world-chart" data={data} dataSeries={dataSeries}/>
|
||||
return <WorldPerformanceGraph id="server-performance-world-chart" data={data} dataSeries={dataSeries}
|
||||
pluginHistorySeries={pluginHistorySeries}/>
|
||||
}
|
||||
|
||||
const DiskGraphTab = ({data, dataSeries, loadingError}) => {
|
||||
const DiskGraphTab = ({data, dataSeries, pluginHistorySeries, loadingError}) => {
|
||||
if (loadingError) return <ErrorViewBody error={loadingError}/>
|
||||
if (!dataSeries) return <ChartLoader style={{height: "450px"}}/>;
|
||||
|
||||
return <DiskPerformanceGraph id="server-performance-disk-chart" data={data} dataSeries={dataSeries}/>
|
||||
return <DiskPerformanceGraph id="server-performance-disk-chart" data={data} dataSeries={dataSeries}
|
||||
pluginHistorySeries={pluginHistorySeries}/>
|
||||
}
|
||||
|
||||
const PingGraphTab = ({identifier}) => {
|
||||
@ -61,37 +67,79 @@ const PingGraphTab = ({identifier}) => {
|
||||
|
||||
const PerformanceGraphsCard = () => {
|
||||
const {t} = useTranslation();
|
||||
const {authRequired, hasPermission} = useAuth();
|
||||
|
||||
const {identifier} = useParams();
|
||||
const {data, loadingError} = useDataRequest(fetchOptimizedPerformance, [identifier]);
|
||||
const [parsedData, setParsedData] = useState(undefined)
|
||||
const [parsedData, setParsedData] = useState(undefined);
|
||||
const {
|
||||
data: pluginHistory,
|
||||
loadingError: pluginHistoryLoadingError
|
||||
} = useDataRequest(fetchPluginHistory, [identifier], authRequired && hasPermission('page.server.plugin.history'));
|
||||
const [pluginHistorySeries, setPluginHistorySeries] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
mapPerformanceDataToSeries(data.values).then(parsed => setParsedData(parsed))
|
||||
}
|
||||
}, [data, setParsedData]);
|
||||
useEffect(() => {
|
||||
// https://stackoverflow.com/a/34890276/20825073
|
||||
const groupBy = function (xs, key) {
|
||||
return xs.reduce(function (rv, x) {
|
||||
(rv[x[key]] = rv[x[key]] || []).push(x);
|
||||
return rv;
|
||||
}, {});
|
||||
};
|
||||
|
||||
if (pluginHistory) {
|
||||
const grouped = groupBy(pluginHistory.history.reverse(), 'modified');
|
||||
setPluginHistorySeries({
|
||||
type: 'flags',
|
||||
accessibility: {
|
||||
exposeAsGroupOnly: true,
|
||||
description: t('html.label.pluginVersionHistory')
|
||||
},
|
||||
tooltip: {headerFormat: ''},
|
||||
data: Object.entries(grouped).map(entry => {
|
||||
const installedLines = entry[1].filter(p => p.version).map(plugin => plugin.name + ': ' + plugin.version).join(', <br>');
|
||||
const uninstalledLines = entry[1].filter(p => !p.version).map(plugin => plugin.name).join(', <br>');
|
||||
return {
|
||||
x: entry[0],
|
||||
title: entry[1].length,
|
||||
text: (installedLines.length ? '<b>' + t('html.label.installed') + '</b><br>' + installedLines : '') +
|
||||
(uninstalledLines.length ? '<b>' + t('html.label.uninstalled') + '</b><br>' + uninstalledLines : '')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}, [pluginHistory, setPluginHistorySeries, t]);
|
||||
|
||||
return <Card id={"performance-graphs"}>
|
||||
<CardTabs tabs={[
|
||||
{
|
||||
name: t('html.label.all'), icon: faGears, color: 'blue-grey', href: 'all',
|
||||
element: <AllGraphTab data={data} dataSeries={parsedData} loadingError={loadingError}/>
|
||||
element: <AllGraphTab data={data} dataSeries={parsedData} pluginHistorySeries={pluginHistorySeries}
|
||||
loadingError={loadingError || pluginHistoryLoadingError}/>
|
||||
}, {
|
||||
name: t('html.label.tps'), icon: faTachometerAlt, color: 'red', href: 'tps',
|
||||
element: <TpsGraphTab data={data} dataSeries={parsedData} loadingError={loadingError}/>
|
||||
element: <TpsGraphTab data={data} dataSeries={parsedData} pluginHistorySeries={pluginHistorySeries}
|
||||
loadingError={loadingError || pluginHistoryLoadingError}/>
|
||||
}, {
|
||||
name: t('html.label.cpuRam'), icon: faMicrochip, color: 'light-green', href: 'cpu-ram',
|
||||
element: <CpuRamGraphTab data={data} dataSeries={parsedData} loadingError={loadingError}/>
|
||||
element: <CpuRamGraphTab data={data} dataSeries={parsedData} pluginHistorySeries={pluginHistorySeries}
|
||||
loadingError={loadingError || pluginHistoryLoadingError}/>
|
||||
}, {
|
||||
name: t('html.label.world'), icon: faMap, color: 'purple', href: 'world-load',
|
||||
element: <WorldGraphTab data={data} dataSeries={parsedData} loadingError={loadingError}/>
|
||||
element: <WorldGraphTab data={data} dataSeries={parsedData} pluginHistorySeries={pluginHistorySeries}
|
||||
loadingError={loadingError || pluginHistoryLoadingError}/>
|
||||
}, {
|
||||
name: t('html.label.ping'), icon: faSignal, color: 'amber', href: 'ping',
|
||||
element: <PingGraphTab identifier={identifier}/>
|
||||
}, {
|
||||
name: t('html.label.diskSpace'), icon: faHdd, color: 'green', href: 'disk',
|
||||
element: <DiskGraphTab data={data} dataSeries={parsedData} loadingError={loadingError}/>
|
||||
element: <DiskGraphTab data={data} dataSeries={parsedData} pluginHistorySeries={pluginHistorySeries}
|
||||
loadingError={loadingError || pluginHistoryLoadingError}/>
|
||||
},
|
||||
]}/>
|
||||
</Card>
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import PerformanceAsNumbersTable from "../../../table/PerformanceAsNumbersTable";
|
||||
import CardHeader from "../../CardHeader";
|
||||
import {faBookOpen} from "@fortawesome/free-solid-svg-icons";
|
||||
import {Card} from "react-bootstrap";
|
||||
import {faBookOpen, faInfoCircle} from "@fortawesome/free-solid-svg-icons";
|
||||
import {Alert, Card} from "react-bootstrap";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
|
||||
const PerformanceAsNumbersCard = ({data, servers}) => {
|
||||
const {t} = useTranslation();
|
||||
@ -15,11 +16,15 @@ const PerformanceAsNumbersCard = ({data, servers}) => {
|
||||
: (noData7d ? <p className={"alert alert-warning mb-0"}>{t('html.description.noData7d')}</p>
|
||||
: (noData24h ? <p className={"alert alert-warning mb-0"}>{t('html.description.noData24h')}</p>
|
||||
: ''));
|
||||
const dataIncludesGameServers = !servers || Boolean(servers.filter(server => !server.proxy).length);
|
||||
|
||||
return (
|
||||
<Card id={"performance-as-numbers"}>
|
||||
<CardHeader icon={faBookOpen} color="blue-grey" label={"html.label.performanceAsNumbers"}/>
|
||||
{noDataAlert}
|
||||
{!dataIncludesGameServers && <Alert className='alert-warning mb-0'>
|
||||
<FontAwesomeIcon icon={faInfoCircle}/> {t('html.description.performanceNoGameServers')}
|
||||
</Alert>}
|
||||
<PerformanceAsNumbersTable data={data} servers={servers}/>
|
||||
</Card>
|
||||
)
|
||||
|
@ -62,7 +62,7 @@ const yAxis = [
|
||||
}
|
||||
]
|
||||
|
||||
const AllPerformanceGraph = ({id, data, dataSeries}) => {
|
||||
const AllPerformanceGraph = ({id, data, dataSeries, pluginHistorySeries}) => {
|
||||
const {t} = useTranslation();
|
||||
const {graphTheming, nightModeEnabled} = useTheme();
|
||||
const {timeZoneOffsetMinutes} = useMetadata();
|
||||
@ -178,9 +178,9 @@ const AllPerformanceGraph = ({id, data, dataSeries}) => {
|
||||
time: {
|
||||
timezoneOffset: timeZoneOffsetMinutes
|
||||
},
|
||||
series: [series.playersOnline, series.tps, series.cpu, series.ram, series.entities, series.chunks]
|
||||
series: [series.playersOnline, series.tps, series.cpu, series.ram, series.entities, series.chunks, pluginHistorySeries]
|
||||
});
|
||||
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t, timeZoneOffsetMinutes])
|
||||
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t, timeZoneOffsetMinutes, pluginHistorySeries])
|
||||
|
||||
return (
|
||||
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||
|
@ -9,7 +9,7 @@ import {withReducedSaturation} from "../../../util/colors";
|
||||
import Accessibility from "highcharts/modules/accessibility";
|
||||
import {useMetadata} from "../../../hooks/metadataHook";
|
||||
|
||||
const CpuRamPerformanceGraph = ({id, data, dataSeries}) => {
|
||||
const CpuRamPerformanceGraph = ({id, data, dataSeries, pluginHistorySeries}) => {
|
||||
const {t} = useTranslation();
|
||||
const {graphTheming, nightModeEnabled} = useTheme();
|
||||
const {timeZoneOffsetMinutes} = useMetadata();
|
||||
@ -90,9 +90,9 @@ const CpuRamPerformanceGraph = ({id, data, dataSeries}) => {
|
||||
time: {
|
||||
timezoneOffset: timeZoneOffsetMinutes
|
||||
},
|
||||
series: [series.playersOnline, series.cpu, series.ram]
|
||||
series: [series.playersOnline, series.cpu, series.ram, pluginHistorySeries]
|
||||
});
|
||||
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t, timeZoneOffsetMinutes])
|
||||
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t, timeZoneOffsetMinutes, pluginHistorySeries])
|
||||
|
||||
return (
|
||||
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||
|
@ -9,7 +9,7 @@ import {withReducedSaturation} from "../../../util/colors";
|
||||
import Accessibility from "highcharts/modules/accessibility";
|
||||
import {useMetadata} from "../../../hooks/metadataHook";
|
||||
|
||||
const DiskPerformanceGraph = ({id, data, dataSeries}) => {
|
||||
const DiskPerformanceGraph = ({id, data, dataSeries, pluginHistorySeries}) => {
|
||||
const {t} = useTranslation();
|
||||
const {graphTheming, nightModeEnabled} = useTheme();
|
||||
const {timeZoneOffsetMinutes} = useMetadata();
|
||||
@ -71,9 +71,9 @@ const DiskPerformanceGraph = ({id, data, dataSeries}) => {
|
||||
time: {
|
||||
timezoneOffset: timeZoneOffsetMinutes
|
||||
},
|
||||
series: [series.disk]
|
||||
series: [series.disk, pluginHistorySeries]
|
||||
});
|
||||
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t, timeZoneOffsetMinutes])
|
||||
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t, timeZoneOffsetMinutes, pluginHistorySeries])
|
||||
|
||||
return (
|
||||
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||
|
@ -9,7 +9,7 @@ import {withReducedSaturation} from "../../../util/colors";
|
||||
import Accessibility from "highcharts/modules/accessibility";
|
||||
import {useMetadata} from "../../../hooks/metadataHook";
|
||||
|
||||
const TpsPerformanceGraph = ({id, data, dataSeries}) => {
|
||||
const TpsPerformanceGraph = ({id, data, dataSeries, pluginHistorySeries}) => {
|
||||
const {t} = useTranslation();
|
||||
const {graphTheming, nightModeEnabled} = useTheme();
|
||||
const {timeZoneOffsetMinutes} = useMetadata();
|
||||
@ -87,9 +87,9 @@ const TpsPerformanceGraph = ({id, data, dataSeries}) => {
|
||||
time: {
|
||||
timezoneOffset: timeZoneOffsetMinutes
|
||||
},
|
||||
series: [series.playersOnline, series.tps]
|
||||
series: [series.playersOnline, series.tps, pluginHistorySeries]
|
||||
});
|
||||
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t, timeZoneOffsetMinutes])
|
||||
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t, timeZoneOffsetMinutes, pluginHistorySeries])
|
||||
|
||||
return (
|
||||
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||
|
@ -9,7 +9,7 @@ import {withReducedSaturation} from "../../../util/colors";
|
||||
import Accessibility from "highcharts/modules/accessibility";
|
||||
import {useMetadata} from "../../../hooks/metadataHook";
|
||||
|
||||
const WorldPerformanceGraph = ({id, data, dataSeries}) => {
|
||||
const WorldPerformanceGraph = ({id, data, dataSeries, pluginHistorySeries}) => {
|
||||
const {t} = useTranslation();
|
||||
const {graphTheming, nightModeEnabled} = useTheme();
|
||||
const {timeZoneOffsetMinutes} = useMetadata();
|
||||
@ -89,9 +89,9 @@ const WorldPerformanceGraph = ({id, data, dataSeries}) => {
|
||||
time: {
|
||||
timezoneOffset: timeZoneOffsetMinutes
|
||||
},
|
||||
series: [series.playersOnline, series.entities, series.chunks]
|
||||
series: [series.playersOnline, series.entities, series.chunks, pluginHistorySeries]
|
||||
});
|
||||
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t, timeZoneOffsetMinutes])
|
||||
}, [data, dataSeries, graphTheming, nightModeEnabled, id, t, timeZoneOffsetMinutes, pluginHistorySeries])
|
||||
|
||||
return (
|
||||
<div className="chart-area" style={{height: "450px"}} id={id}>
|
||||
|
@ -333,3 +333,9 @@ const fetchNetworkPlayerJoinAddresses = async (timestamp) => {
|
||||
if (staticSite) url = `/data/joinAddresses.json`;
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
||||
|
||||
export const fetchPluginHistory = async (timestamp, identifier) => {
|
||||
let url = `/v1/pluginHistory?server=${identifier}`;
|
||||
if (staticSite) url = `/data/pluginHistory-${identifier}.json`;
|
||||
return doGetRequest(url, timestamp);
|
||||
}
|
@ -1496,3 +1496,7 @@ ul.filters {
|
||||
.group-help i {
|
||||
color: var(--color-plan)
|
||||
}
|
||||
|
||||
.nav-item.disabled {
|
||||
opacity: 30%;
|
||||
}
|
@ -4,6 +4,7 @@ import {Outlet} from "react-router-dom";
|
||||
import {useNavigation} from "../../hooks/navigationHook";
|
||||
import {
|
||||
faChartLine,
|
||||
faCodeCompare,
|
||||
faCogs,
|
||||
faCubes,
|
||||
faGlobe,
|
||||
@ -32,13 +33,14 @@ const HelpModal = React.lazy(() => import("../../components/modal/HelpModal"));
|
||||
|
||||
const NetworkSidebar = () => {
|
||||
const {t, i18n} = useTranslation();
|
||||
const {authRequired} = useAuth();
|
||||
const {sidebarItems, setSidebarItems} = useNavigation();
|
||||
const {networkMetadata} = useMetadata();
|
||||
const {extensionData} = useServerExtensionContext();
|
||||
|
||||
useEffect(() => {
|
||||
const servers = networkMetadata?.servers || [];
|
||||
const items = [
|
||||
let items = [
|
||||
{
|
||||
name: 'html.label.networkOverview',
|
||||
icon: faInfoCircle,
|
||||
@ -119,6 +121,13 @@ const NetworkSidebar = () => {
|
||||
},
|
||||
{},
|
||||
{name: 'html.label.plugins', permission: 'page.network.plugins'},
|
||||
{
|
||||
name: 'html.label.pluginHistory',
|
||||
icon: faCodeCompare,
|
||||
href: "plugin-history",
|
||||
permission: 'page.network.plugin.history',
|
||||
authRequired: true
|
||||
},
|
||||
{
|
||||
name: 'html.label.pluginsOverview',
|
||||
icon: faCubes,
|
||||
@ -147,10 +156,13 @@ const NetworkSidebar = () => {
|
||||
{name: 'html.label.query', icon: faSearch, href: "/query", permission: 'access.query'}
|
||||
);
|
||||
}
|
||||
// Filter out items that need authentication
|
||||
items = items
|
||||
.filter(item => !item.authRequired || (authRequired && item.authRequired))
|
||||
|
||||
setSidebarItems(items);
|
||||
window.document.title = `Plan | Network`;
|
||||
}, [t, i18n, extensionData, setSidebarItems, networkMetadata])
|
||||
}, [t, i18n, extensionData, setSidebarItems, networkMetadata, authRequired])
|
||||
|
||||
return (
|
||||
<Sidebar items={sidebarItems}/>
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
faCampground,
|
||||
faChartArea,
|
||||
faChartLine,
|
||||
faCodeCompare,
|
||||
faCogs,
|
||||
faCubes,
|
||||
faGlobe,
|
||||
@ -35,11 +36,12 @@ const HelpModal = React.lazy(() => import("../../components/modal/HelpModal"));
|
||||
|
||||
const ServerSidebar = () => {
|
||||
const {t, i18n} = useTranslation();
|
||||
const {authRequired} = useAuth();
|
||||
const {sidebarItems, setSidebarItems} = useNavigation();
|
||||
const {extensionData} = useServerExtensionContext();
|
||||
|
||||
useEffect(() => {
|
||||
const items = [
|
||||
let items = [
|
||||
{
|
||||
name: 'html.label.serverOverview',
|
||||
icon: faInfoCircle,
|
||||
@ -113,6 +115,13 @@ const ServerSidebar = () => {
|
||||
{name: 'html.label.performance', icon: faCogs, href: "performance", permission: 'page.server.performance'},
|
||||
{},
|
||||
{name: 'html.label.plugins', permission: 'page.server.plugins'},
|
||||
{
|
||||
name: 'html.label.pluginHistory',
|
||||
icon: faCodeCompare,
|
||||
href: "plugin-history",
|
||||
permission: 'page.server.plugin.history',
|
||||
authRequired: true
|
||||
},
|
||||
{
|
||||
name: 'html.label.pluginsOverview',
|
||||
icon: faCubes,
|
||||
@ -142,9 +151,12 @@ const ServerSidebar = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// Filter out items that need authentication
|
||||
items = items
|
||||
.filter(item => !item.authRequired || (authRequired && item.authRequired))
|
||||
setSidebarItems(items);
|
||||
window.document.title = `Plan | Server Analysis`;
|
||||
}, [t, i18n, extensionData, setSidebarItems])
|
||||
}, [t, i18n, extensionData, setSidebarItems, authRequired])
|
||||
|
||||
return (
|
||||
<Sidebar items={sidebarItems}/>
|
||||
|
@ -0,0 +1,72 @@
|
||||
import {Col, InputGroup} from "react-bootstrap";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import LoadIn from "../../components/animation/LoadIn";
|
||||
import ExtendableRow from "../../components/layout/extension/ExtendableRow";
|
||||
import {useAuth} from "../../hooks/authenticationHook";
|
||||
import PluginHistoryCard from "../../components/cards/common/PluginHistoryCard";
|
||||
import {useDataRequest} from "../../hooks/dataFetchHook";
|
||||
import {fetchPluginHistory} from "../../service/serverService";
|
||||
import PluginCurrentCard from "../../components/cards/common/PluginCurrentCard";
|
||||
import {useMetadata} from "../../hooks/metadataHook";
|
||||
import Select from "../../components/input/Select";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import InputGroupText from "react-bootstrap/InputGroupText";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faServer} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const NetworkPluginHistory = () => {
|
||||
const {t} = useTranslation();
|
||||
const {authRequired, hasPermission} = useAuth();
|
||||
|
||||
const seeHistory = authRequired && hasPermission('page.network.plugin.history');
|
||||
|
||||
const {networkMetadata} = useMetadata();
|
||||
const [serverOptions, setServerOptions] = useState([]);
|
||||
const [selectedOption, setSelectedOption] = useState(0);
|
||||
const [identifier, setIdentifier] = useState(undefined);
|
||||
useEffect(() => {
|
||||
if (networkMetadata) {
|
||||
const options = networkMetadata.servers;
|
||||
setServerOptions(options);
|
||||
|
||||
const indexOfProxy = options
|
||||
.findIndex(option => option.serverName === networkMetadata.currentServer.serverName);
|
||||
|
||||
setSelectedOption(indexOfProxy);
|
||||
}
|
||||
}, [networkMetadata, setSelectedOption, setServerOptions]);
|
||||
useEffect(() => {
|
||||
if (serverOptions.length) {
|
||||
setIdentifier(serverOptions[selectedOption].serverUUID);
|
||||
}
|
||||
}, [selectedOption, serverOptions])
|
||||
|
||||
let {data, loadingError} = useDataRequest(fetchPluginHistory, [identifier], Boolean(identifier) && seeHistory);
|
||||
if (!identifier) data = {history: []};
|
||||
return (
|
||||
<LoadIn>
|
||||
{seeHistory && <section id="network-plugin-history">
|
||||
<ExtendableRow id={'row-network-plugin-history-0'}>
|
||||
<Col md={4} className={"mb-4"}>
|
||||
<InputGroup>
|
||||
<InputGroupText><FontAwesomeIcon icon={faServer}/> {t('html.label.serverSelector')}
|
||||
</InputGroupText>
|
||||
<Select options={serverOptions.map(server => server.serverName)}
|
||||
selectedIndex={selectedOption} setSelectedIndex={setSelectedOption}/>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
</ExtendableRow>
|
||||
<ExtendableRow id={'row-network-plugin-history-1'}>
|
||||
<Col md={6}>
|
||||
<PluginCurrentCard data={data} loadingError={loadingError}/>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<PluginHistoryCard data={data} loadingError={loadingError}/>
|
||||
</Col>
|
||||
</ExtendableRow>
|
||||
</section>}
|
||||
</LoadIn>
|
||||
)
|
||||
}
|
||||
|
||||
export default NetworkPluginHistory;
|
34
Plan/react/dashboard/src/views/server/ServerPluginHistory.js
Normal file
34
Plan/react/dashboard/src/views/server/ServerPluginHistory.js
Normal file
@ -0,0 +1,34 @@
|
||||
import {Col} from "react-bootstrap";
|
||||
import React from "react";
|
||||
import LoadIn from "../../components/animation/LoadIn";
|
||||
import {useParams} from "react-router-dom";
|
||||
import ExtendableRow from "../../components/layout/extension/ExtendableRow";
|
||||
import {useAuth} from "../../hooks/authenticationHook";
|
||||
import PluginHistoryCard from "../../components/cards/common/PluginHistoryCard";
|
||||
import {useDataRequest} from "../../hooks/dataFetchHook";
|
||||
import {fetchPluginHistory} from "../../service/serverService";
|
||||
import PluginCurrentCard from "../../components/cards/common/PluginCurrentCard";
|
||||
|
||||
const ServerPluginHistory = () => {
|
||||
const {authRequired, hasPermission} = useAuth();
|
||||
const {identifier} = useParams();
|
||||
|
||||
const seeHistory = authRequired && hasPermission('page.server.plugin.history');
|
||||
const {data, loadingError} = useDataRequest(fetchPluginHistory, [identifier], seeHistory);
|
||||
return (
|
||||
<LoadIn>
|
||||
{seeHistory && <section id="server-plugin-history">
|
||||
<ExtendableRow id={'row-server-plugin-history-0'}>
|
||||
<Col md={6}>
|
||||
<PluginCurrentCard data={data} loadingError={loadingError}/>
|
||||
</Col>
|
||||
<Col md={6}>
|
||||
<PluginHistoryCard data={data} loadingError={loadingError}/>
|
||||
</Col>
|
||||
</ExtendableRow>
|
||||
</section>}
|
||||
</LoadIn>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServerPluginHistory;
|
@ -16,10 +16,12 @@
|
||||
*/
|
||||
package com.djrapitops.plan.gathering;
|
||||
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
import org.spongepowered.api.Game;
|
||||
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
|
||||
import org.spongepowered.api.world.chunk.WorldChunk;
|
||||
import org.spongepowered.api.world.server.ServerWorld;
|
||||
import org.spongepowered.plugin.PluginContainer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@ -82,4 +84,14 @@ public class SpongeSensor implements ServerSensor<ServerWorld> {
|
||||
public List<String> getOnlinePlayerNames() {
|
||||
return game.server().onlinePlayers().stream().map(ServerPlayer::name).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PluginMetadata> getInstalledPlugins() {
|
||||
return game.pluginManager().plugins().stream()
|
||||
.map(PluginContainer::metadata)
|
||||
.map(metadata -> new PluginMetadata(
|
||||
metadata.name().orElse(metadata.id()),
|
||||
metadata.version().toString()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import com.djrapitops.plan.delivery.webserver.configuration.AddressAllowList;
|
||||
import com.djrapitops.plan.extension.ExtensionServerDataUpdater;
|
||||
import com.djrapitops.plan.gathering.ShutdownDataPreservation;
|
||||
import com.djrapitops.plan.gathering.ShutdownHook;
|
||||
import com.djrapitops.plan.gathering.timed.InstalledPluginGatheringTask;
|
||||
import com.djrapitops.plan.gathering.timed.ServerTPSCounter;
|
||||
import com.djrapitops.plan.gathering.timed.SpongePingCounter;
|
||||
import com.djrapitops.plan.gathering.timed.SystemUsageBuffer;
|
||||
@ -98,4 +99,8 @@ public interface SpongeTaskModule {
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindAddressAllowListUpdateTask(AddressAllowList addressAllowList);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindInstalledPluginGatheringTask(InstalledPluginGatheringTask installedPluginGatheringTask);
|
||||
}
|
||||
|
@ -17,8 +17,10 @@
|
||||
package com.djrapitops.plan.gathering;
|
||||
|
||||
import com.djrapitops.plan.PlanVelocity;
|
||||
import com.djrapitops.plan.gathering.domain.PluginMetadata;
|
||||
import com.djrapitops.plan.identification.properties.VelocityRedisCheck;
|
||||
import com.djrapitops.plan.identification.properties.VelocityRedisPlayersOnlineSupplier;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -34,6 +36,7 @@ public class VelocitySensor implements ServerSensor<Object> {
|
||||
|
||||
private final IntSupplier onlinePlayerCountSupplier;
|
||||
private final Supplier<Collection<Player>> getPlayers;
|
||||
private final Supplier<Collection<PluginContainer>> getPlugins;
|
||||
|
||||
@Inject
|
||||
public VelocitySensor(PlanVelocity plugin) {
|
||||
@ -41,6 +44,7 @@ public class VelocitySensor implements ServerSensor<Object> {
|
||||
onlinePlayerCountSupplier = VelocityRedisCheck.isClassAvailable()
|
||||
? new VelocityRedisPlayersOnlineSupplier()
|
||||
: plugin.getProxy()::getPlayerCount;
|
||||
getPlugins = () -> plugin.getProxy().getPluginManager().getPlugins();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -62,4 +66,14 @@ public class VelocitySensor implements ServerSensor<Object> {
|
||||
public boolean usingRedisBungee() {
|
||||
return VelocityRedisCheck.isClassAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PluginMetadata> getInstalledPlugins() {
|
||||
return getPlugins.get().stream()
|
||||
.map(PluginContainer::getDescription)
|
||||
.map(description -> new PluginMetadata(
|
||||
description.getName().orElse(description.getId()),
|
||||
description.getVersion().orElse("html.label.installed")))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieExpiryCleanupTask
|
||||
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.InstalledPluginGatheringTask;
|
||||
import com.djrapitops.plan.gathering.timed.ProxyTPSCounter;
|
||||
import com.djrapitops.plan.gathering.timed.SystemUsageBuffer;
|
||||
import com.djrapitops.plan.gathering.timed.VelocityPingCounter;
|
||||
@ -87,4 +88,8 @@ public interface VelocityTaskModule {
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindAddressAllowListUpdateTask(AddressAllowList addressAllowList);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindInstalledPluginGatheringTask(InstalledPluginGatheringTask installedPluginGatheringTask);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user