Skip to content

Commit 5a2bdaf

Browse files
authored
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
1 parent 61db136 commit 5a2bdaf

File tree

66 files changed

+1464
-51
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1464
-51
lines changed

Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/BukkitSensor.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616
*/
1717
package com.djrapitops.plan.gathering;
1818

19+
import com.djrapitops.plan.gathering.domain.PluginMetadata;
1920
import org.bukkit.Bukkit;
2021
import org.bukkit.Server;
2122
import org.bukkit.World;
2223
import org.bukkit.entity.Player;
24+
import org.bukkit.plugin.Plugin;
2325

2426
import javax.inject.Inject;
2527
import javax.inject.Singleton;
28+
import java.util.Arrays;
2629
import java.util.List;
2730
import java.util.stream.Collectors;
2831

@@ -129,4 +132,12 @@ public List<String> getOnlinePlayerNames() {
129132
.map(Player::getName)
130133
.collect(Collectors.toList());
131134
}
135+
136+
@Override
137+
public List<PluginMetadata> getInstalledPlugins() {
138+
return Arrays.stream(Bukkit.getPluginManager().getPlugins())
139+
.map(Plugin::getDescription)
140+
.map(description -> new PluginMetadata(description.getName(), description.getVersion()))
141+
.collect(Collectors.toList());
142+
}
132143
}

Plan/bukkit/src/main/java/com/djrapitops/plan/modules/bukkit/BukkitTaskModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.djrapitops.plan.gathering.ShutdownDataPreservation;
2727
import com.djrapitops.plan.gathering.ShutdownHook;
2828
import com.djrapitops.plan.gathering.timed.BukkitPingCounter;
29+
import com.djrapitops.plan.gathering.timed.InstalledPluginGatheringTask;
2930
import com.djrapitops.plan.gathering.timed.ServerTPSCounter;
3031
import com.djrapitops.plan.gathering.timed.SystemUsageBuffer;
3132
import com.djrapitops.plan.settings.upkeep.ConfigStoreTask;
@@ -108,4 +109,8 @@ public interface BukkitTaskModule {
108109
@Binds
109110
@IntoSet
110111
TaskSystem.Task bindAddressAllowListUpdateTask(AddressAllowList addressAllowList);
112+
113+
@Binds
114+
@IntoSet
115+
TaskSystem.Task bindInstalledPluginGatheringTask(InstalledPluginGatheringTask installedPluginGatheringTask);
111116
}

Plan/bungeecord/src/main/java/com/djrapitops/plan/gathering/BungeeSensor.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
package com.djrapitops.plan.gathering;
1818

1919
import com.djrapitops.plan.PlanBungee;
20+
import com.djrapitops.plan.gathering.domain.PluginMetadata;
2021
import com.djrapitops.plan.identification.properties.RedisCheck;
2122
import com.djrapitops.plan.identification.properties.RedisPlayersOnlineSupplier;
2223
import net.md_5.bungee.api.connection.ProxiedPlayer;
24+
import net.md_5.bungee.api.plugin.Plugin;
2325

2426
import javax.inject.Inject;
2527
import javax.inject.Singleton;
@@ -35,11 +37,13 @@ public class BungeeSensor implements ServerSensor<Object> {
3537
private final IntSupplier onlinePlayerCountSupplier;
3638
private final IntSupplier onlinePlayerCountBungee;
3739
private final Supplier<Collection<ProxiedPlayer>> getPlayers;
40+
private final Supplier<Collection<Plugin>> getPlugins;
3841

3942
@Inject
4043
public BungeeSensor(PlanBungee plugin) {
4144
getPlayers = plugin.getProxy()::getPlayers;
4245
onlinePlayerCountBungee = plugin.getProxy()::getOnlineCount;
46+
getPlugins = plugin.getProxy().getPluginManager()::getPlugins;
4347
onlinePlayerCountSupplier = RedisCheck.isClassAvailable() ? new RedisPlayersOnlineSupplier() : onlinePlayerCountBungee;
4448
}
4549

@@ -63,4 +67,12 @@ public List<String> getOnlinePlayerNames() {
6367
public boolean usingRedisBungee() {
6468
return RedisCheck.isClassAvailable();
6569
}
70+
71+
@Override
72+
public List<PluginMetadata> getInstalledPlugins() {
73+
return getPlugins.get().stream()
74+
.map(Plugin::getDescription)
75+
.map(description -> new PluginMetadata(description.getName(), description.getVersion()))
76+
.collect(Collectors.toList());
77+
}
6678
}

Plan/bungeecord/src/main/java/com/djrapitops/plan/modules/bungee/BungeeTaskModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.djrapitops.plan.delivery.webserver.configuration.AddressAllowList;
2424
import com.djrapitops.plan.extension.ExtensionServerDataUpdater;
2525
import com.djrapitops.plan.gathering.timed.BungeePingCounter;
26+
import com.djrapitops.plan.gathering.timed.InstalledPluginGatheringTask;
2627
import com.djrapitops.plan.gathering.timed.ProxyTPSCounter;
2728
import com.djrapitops.plan.gathering.timed.SystemUsageBuffer;
2829
import com.djrapitops.plan.settings.upkeep.NetworkConfigStoreTask;
@@ -87,4 +88,8 @@ public interface BungeeTaskModule {
8788
@Binds
8889
@IntoSet
8990
TaskSystem.Task bindAddressAllowListUpdateTask(AddressAllowList addressAllowList);
91+
92+
@Binds
93+
@IntoSet
94+
TaskSystem.Task bindInstalledPluginGatheringTask(InstalledPluginGatheringTask installedPluginGatheringTask);
9095
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* This file is part of Player Analytics (Plan).
3+
*
4+
* Plan is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License v3 as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Plan is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public License
15+
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
package com.djrapitops.plan.delivery.domain;
18+
19+
import org.jetbrains.annotations.Nullable;
20+
21+
import java.util.Objects;
22+
23+
/**
24+
* Represents plugin version history.
25+
* <p>
26+
* If version is null the plugin was uninstalled at that time.
27+
*
28+
* @author AuroraLS3
29+
*/
30+
public class PluginHistoryMetadata {
31+
32+
private final String name;
33+
@Nullable
34+
private final String version;
35+
private final long modified;
36+
37+
public PluginHistoryMetadata(String name, @Nullable String version, long modified) {
38+
this.name = name;
39+
this.version = version;
40+
this.modified = modified;
41+
}
42+
43+
public String getName() {
44+
return name;
45+
}
46+
47+
@Nullable
48+
public String getVersion() {
49+
return version;
50+
}
51+
52+
public long getModified() {
53+
return modified;
54+
}
55+
56+
@Override
57+
public boolean equals(Object o) {
58+
if (this == o) return true;
59+
if (o == null || getClass() != o.getClass()) return false;
60+
PluginHistoryMetadata that = (PluginHistoryMetadata) o;
61+
return getModified() == that.getModified() && Objects.equals(getName(), that.getName()) && Objects.equals(getVersion(), that.getVersion());
62+
}
63+
64+
@Override
65+
public int hashCode() {
66+
return Objects.hash(getName(), getVersion(), getModified());
67+
}
68+
69+
@Override
70+
public String toString() {
71+
return "PluginHistoryMetadata{" +
72+
"name='" + name + '\'' +
73+
", version='" + version + '\'' +
74+
", modified=" + modified +
75+
'}';
76+
}
77+
}

Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/WebPermission.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public enum WebPermission implements Supplier<String>, Lang {
5555
PAGE_NETWORK_GEOLOCATIONS_PING_PER_COUNTRY("See Ping Per Country table"),
5656
PAGE_NETWORK_PLAYERS("See Player list -tab"),
5757
PAGE_NETWORK_PERFORMANCE("See network Performance tab"),
58+
PAGE_NETWORK_PLUGIN_HISTORY("See Plugin History across the network"),
5859
PAGE_NETWORK_PLUGINS("See Plugins tab of Proxy"),
5960

6061
PAGE_SERVER("See all of server page"),
@@ -90,6 +91,7 @@ public enum WebPermission implements Supplier<String>, Lang {
9091
PAGE_SERVER_PERFORMANCE("See Performance tab"),
9192
PAGE_SERVER_PERFORMANCE_GRAPHS("See Performance graphs"),
9293
PAGE_SERVER_PERFORMANCE_OVERVIEW("See Performance numbers"),
94+
PAGE_SERVER_PLUGIN_HISTORY("See Plugin History"),
9395
PAGE_SERVER_PLUGINS("See Plugins -tabs of servers"),
9496

9597
PAGE_PLAYER("See all of player page"),
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* This file is part of Player Analytics (Plan).
3+
*
4+
* Plan is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License v3 as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Plan is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public License
15+
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
package com.djrapitops.plan.delivery.domain.datatransfer;
18+
19+
import com.djrapitops.plan.delivery.domain.PluginHistoryMetadata;
20+
21+
import java.util.List;
22+
import java.util.Objects;
23+
24+
/**
25+
* History of plugin versions, sorted most recent first.
26+
*
27+
* @author AuroraLS3
28+
*/
29+
public class PluginHistoryDto {
30+
31+
private final List<PluginHistoryMetadata> history;
32+
33+
public PluginHistoryDto(List<PluginHistoryMetadata> history) {
34+
this.history = history;
35+
}
36+
37+
public List<PluginHistoryMetadata> getHistory() {
38+
return history;
39+
}
40+
41+
@Override
42+
public boolean equals(Object o) {
43+
if (this == o) return true;
44+
if (o == null || getClass() != o.getClass()) return false;
45+
PluginHistoryDto that = (PluginHistoryDto) o;
46+
return Objects.equals(getHistory(), that.getHistory());
47+
}
48+
49+
@Override
50+
public int hashCode() {
51+
return Objects.hash(getHistory());
52+
}
53+
54+
@Override
55+
public String toString() {
56+
return "PluginHistoryDto{" +
57+
"history=" + history +
58+
'}';
59+
}
60+
}

Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/RootJSONResolver.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.djrapitops.plan.delivery.webserver.http.WebServer;
2525
import com.djrapitops.plan.delivery.webserver.resolver.json.metadata.PreferencesJSONResolver;
2626
import com.djrapitops.plan.delivery.webserver.resolver.json.metadata.StorePreferencesJSONResolver;
27+
import com.djrapitops.plan.delivery.webserver.resolver.json.plugins.PluginHistoryJSONResolver;
2728
import com.djrapitops.plan.identification.Identifiers;
2829
import dagger.Lazy;
2930

@@ -49,6 +50,7 @@ public class RootJSONResolver {
4950

5051
private final CompositeResolver.Builder readOnlyResourcesBuilder;
5152
private final StorePreferencesJSONResolver storePreferencesJSONResolver;
53+
private final PluginHistoryJSONResolver pluginHistoryJSONResolver;
5254
private CompositeResolver resolver;
5355

5456
@Inject
@@ -84,6 +86,7 @@ public RootJSONResolver(
8486
ExtensionJSONResolver extensionJSONResolver,
8587
RetentionJSONResolver retentionJSONResolver,
8688
PlayerJoinAddressJSONResolver playerJoinAddressJSONResolver,
89+
PluginHistoryJSONResolver pluginHistoryJSONResolver,
8790

8891
PreferencesJSONResolver preferencesJSONResolver,
8992
StorePreferencesJSONResolver storePreferencesJSONResolver,
@@ -127,6 +130,7 @@ public RootJSONResolver(
127130

128131
this.webServer = webServer;
129132
// These endpoints require authentication to be enabled.
133+
this.pluginHistoryJSONResolver = pluginHistoryJSONResolver;
130134
this.webGroupJSONResolver = webGroupJSONResolver;
131135
this.webGroupPermissionJSONResolver = webGroupPermissionJSONResolver;
132136
this.webPermissionJSONResolver = webPermissionJSONResolver;
@@ -149,6 +153,7 @@ public CompositeResolver getResolver() {
149153
.add("saveGroupPermissions", webGroupSaveJSONResolver)
150154
.add("deleteGroup", webGroupDeleteJSONResolver)
151155
.add("storePreferences", storePreferencesJSONResolver)
156+
.add("pluginHistory", pluginHistoryJSONResolver)
152157
.build();
153158
} else {
154159
resolver = readOnlyResourcesBuilder.build();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* This file is part of Player Analytics (Plan).
3+
*
4+
* Plan is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License v3 as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Plan is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public License
15+
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
package com.djrapitops.plan.delivery.webserver.resolver.json.plugins;
18+
19+
import com.djrapitops.plan.delivery.domain.PluginHistoryMetadata;
20+
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
21+
import com.djrapitops.plan.delivery.domain.datatransfer.PluginHistoryDto;
22+
import com.djrapitops.plan.delivery.web.resolver.MimeType;
23+
import com.djrapitops.plan.delivery.web.resolver.Resolver;
24+
import com.djrapitops.plan.delivery.web.resolver.Response;
25+
import com.djrapitops.plan.delivery.web.resolver.request.Request;
26+
import com.djrapitops.plan.identification.Identifiers;
27+
import com.djrapitops.plan.identification.ServerUUID;
28+
import com.djrapitops.plan.storage.database.DBSystem;
29+
import com.djrapitops.plan.storage.database.queries.objects.PluginMetadataQueries;
30+
import com.djrapitops.plan.utilities.dev.Untrusted;
31+
import io.swagger.v3.oas.annotations.Operation;
32+
import io.swagger.v3.oas.annotations.Parameter;
33+
import io.swagger.v3.oas.annotations.enums.ParameterIn;
34+
import io.swagger.v3.oas.annotations.media.Content;
35+
import io.swagger.v3.oas.annotations.media.ExampleObject;
36+
import io.swagger.v3.oas.annotations.media.Schema;
37+
import io.swagger.v3.oas.annotations.parameters.RequestBody;
38+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
39+
import jakarta.ws.rs.GET;
40+
import jakarta.ws.rs.Path;
41+
42+
import javax.inject.Inject;
43+
import javax.inject.Singleton;
44+
import java.util.List;
45+
import java.util.Optional;
46+
47+
/**
48+
* Endpoint for getting plugin version history.
49+
*
50+
* @author AuroraLS3
51+
*/
52+
@Singleton
53+
@Path("/v1/pluginHistory")
54+
public class PluginHistoryJSONResolver implements Resolver {
55+
56+
private final DBSystem dbSystem;
57+
private final Identifiers identifiers;
58+
59+
@Inject
60+
public PluginHistoryJSONResolver(DBSystem dbSystem, Identifiers identifiers) {
61+
this.dbSystem = dbSystem;
62+
this.identifiers = identifiers;
63+
}
64+
65+
@Override
66+
public boolean canAccess(Request request) {
67+
return request.getUser()
68+
.map(user -> user.hasPermission(WebPermission.PAGE_NETWORK_PLUGIN_HISTORY)
69+
|| user.hasPermission(WebPermission.PAGE_SERVER_PLUGIN_HISTORY))
70+
.orElse(false);
71+
}
72+
73+
@Override
74+
@Operation(
75+
description = "Get plugin history for a server since installation of Plan.",
76+
responses = {
77+
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON,
78+
schema = @Schema(implementation = PluginHistoryDto.class))),
79+
},
80+
parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = {
81+
@ExampleObject("Server 1"),
82+
@ExampleObject("1"),
83+
@ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"),
84+
}),
85+
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
86+
)
87+
@GET
88+
public Optional<Response> resolve(@Untrusted Request request) {
89+
return Optional.of(getResponse(request));
90+
}
91+
92+
private Response getResponse(@Untrusted Request request) {
93+
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
94+
List<PluginHistoryMetadata> history = dbSystem.getDatabase().query(PluginMetadataQueries.getPluginHistory(serverUUID));
95+
return Response.builder()
96+
.setMimeType(MimeType.JSON)
97+
.setJSONContent(new PluginHistoryDto(history))
98+
.build();
99+
}
100+
}

0 commit comments

Comments
 (0)