Skip to content

Commit 14e7abd

Browse files
authored
feat: support HTML output (#7)
* feat: support HTML output * feat: add "Display as JSON" button * docs: update screenshot * fix: always use UTF-8 character encoding
1 parent e983997 commit 14e7abd

6 files changed

Lines changed: 125 additions & 12 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
This plugin exposes server information via an HTTP endpoint. It depends on
44
[Nitrado:WebServer](https://github.com/nitrado/hytale-plugin-webserver).
55

6+
![screenshot.png](docs/images/screenshot.png)
7+
68
## Purpose of this Plugin
79

810
When running a Hytale server, external systems often need access to basic server information for

docs/images/screenshot.png

97.8 KB
Loading

src/main/java/net/nitrado/hytale/plugins/query/QueryPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private void registerHandlers() {
4545
var templateEngine = this.webServerPlugin.getTemplateEngineFactory().getEngineFor(this);
4646

4747
try {
48-
webServerPlugin.addServlet(this, "", new QueryServlet(templateEngine, publicAddress));
48+
webServerPlugin.addServlet(this, "", new QueryServlet(webServerPlugin, this, templateEngine, publicAddress));
4949
} catch (Exception e) {
5050
getLogger().at(Level.SEVERE).withCause(e).log("Failed to register route.");
5151
}

src/main/java/net/nitrado/hytale/plugins/query/QueryResponseV1.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,11 @@ public record BasicData(
193193
int maxPlayers,
194194
int currentPlayers,
195195
InetSocketAddress address
196-
) {}
196+
) {
197+
public String formattedAddress() {
198+
return address == null ? null : formatAddress(address);
199+
}
200+
}
197201

198202
public record ServerData(
199203
String name,
@@ -204,7 +208,11 @@ public record ServerData(
204208
String protocolHash,
205209
int maxPlayers,
206210
InetSocketAddress address
207-
) {}
211+
) {
212+
public String formattedAddress() {
213+
return address == null ? null : formatAddress(address);
214+
}
215+
}
208216

209217
public record UniverseData(
210218
int currentPlayers,
@@ -225,7 +233,7 @@ public record PluginData(
225233
String state
226234
) {}
227235

228-
private String formatAddress(InetSocketAddress address) {
236+
private static String formatAddress(InetSocketAddress address) {
229237
String formatted;
230238

231239
formatted = address.getHostString();

src/main/java/net/nitrado/hytale/plugins/query/QueryServlet.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package net.nitrado.hytale.plugins.query;
22

3+
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
34
import jakarta.servlet.http.HttpServlet;
45
import jakarta.servlet.http.HttpServletRequest;
56
import jakarta.servlet.http.HttpServletResponse;
7+
import net.nitrado.hytale.plugins.webserver.WebServerPlugin;
68
import net.nitrado.hytale.plugins.webserver.authentication.HytaleUserPrincipal;
79
import net.nitrado.hytale.plugins.webserver.authorization.RequirePermissions;
10+
import net.nitrado.hytale.plugins.webserver.servlets.TemplateServlet;
811
import net.nitrado.hytale.plugins.webserver.util.RequestUtils;
912
import org.bson.json.JsonWriterSettings;
1013
import org.thymeleaf.TemplateEngine;
@@ -13,8 +16,9 @@
1316

1417
import java.io.IOException;
1518
import java.net.InetSocketAddress;
19+
import java.util.HashMap;
1620

17-
public class QueryServlet extends HttpServlet {
21+
public class QueryServlet extends TemplateServlet {
1822

1923
private static final String JSON_V1 = "application/x.hytale.nitrado.query+json;version=1";
2024
private static final String TEXT_HTML = "text/html";
@@ -23,7 +27,9 @@ public class QueryServlet extends HttpServlet {
2327
private TemplateEngine templateEngine;
2428
private InetSocketAddress publicAddress;
2529

26-
public QueryServlet(TemplateEngine templateEngine, InetSocketAddress publicAddress) {
30+
public QueryServlet(WebServerPlugin parentPlugin, JavaPlugin thisPlugin, TemplateEngine templateEngine, InetSocketAddress publicAddress) {
31+
super(parentPlugin, thisPlugin);
32+
2733
this.templateEngine = templateEngine;
2834
this.publicAddress = publicAddress;
2935
}
@@ -51,8 +57,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
5157

5258
var contentType = RequestUtils.negotiateContentType(
5359
req,
54-
JSON_V1
55-
// TEXT_HTML
60+
JSON_V1,
61+
TEXT_HTML
5662
);
5763

5864
if (contentType == null) {
@@ -71,15 +77,19 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
7177
}
7278

7379
private void handleHtml(HttpServletRequest req, HttpServletResponse resp) throws IOException {
80+
resp.setCharacterEncoding("UTF-8");
7481
resp.setContentType(TEXT_HTML);
7582

76-
var exchange = getWebApplication().buildExchange(req, resp);
77-
var thymeleafContext = new WebContext(exchange);
83+
var response = buildQueryResponse(req);
84+
85+
var vars = new HashMap<String, Object>();
86+
vars.put("response", response);
7887

79-
this.templateEngine.process("nitrado.query", thymeleafContext, resp.getWriter());
88+
this.renderTemplate(req, resp, "nitrado.query", vars);
8089
}
8190

8291
protected void handleJsonV1(HttpServletRequest req, HttpServletResponse resp) throws IOException {
92+
resp.setCharacterEncoding("UTF-8");
8393
resp.setContentType(JSON_V1);
8494

8595
var response = buildQueryResponse(req);

src/main/resources/templates/nitrado.query.html

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,99 @@
33
<title>Nitrado:Query</title>
44
</head>
55
<body>
6-
<section>Sample Text</section>
6+
<section class="container py-4 position-relative">
7+
<a class="btn btn-primary position-absolute top-0 end-0" th:href="@{/Nitrado/Query(output=json)}">Display as JSON</a>
8+
9+
<h1 class="mb-5">Nitrado:Query</h1>
10+
<!-- Basic Data -->
11+
<div th:if="${response.basic != null}" class="mb-4">
12+
<h5>Basic</h5>
13+
<table class="table table-striped">
14+
<tbody>
15+
<tr><th scope="row">Name</th><td th:text="${response.basic.name}"></td></tr>
16+
<tr><th scope="row">Version</th><td th:text="${response.basic.version}"></td></tr>
17+
<tr><th scope="row">Current Players</th><td th:text="${response.basic.currentPlayers}"></td></tr>
18+
<tr><th scope="row">Max Players</th><td th:text="${response.basic.maxPlayers}"></td></tr>
19+
<tr th:if="${response.basic.formattedAddress != null}"><th scope="row">Address</th><td th:text="${response.basic.formattedAddress}"></td></tr>
20+
</tbody>
21+
</table>
22+
</div>
23+
24+
<!-- Server Data -->
25+
<div th:if="${response.server != null}" class="mb-4">
26+
<h5>Server</h5>
27+
<table class="table table-striped">
28+
<tbody>
29+
<tr><th scope="row">Name</th><td th:text="${response.server.name}"></td></tr>
30+
<tr><th scope="row">Version</th><td th:text="${response.server.version}"></td></tr>
31+
<tr><th scope="row">Revision</th><td th:text="${response.server.revision}"></td></tr>
32+
<tr><th scope="row">Patchline</th><td th:text="${response.server.patchline}"></td></tr>
33+
<tr><th scope="row">Protocol Version</th><td th:text="${response.server.protocolVersion}"></td></tr>
34+
<tr><th scope="row">Protocol Hash</th><td th:text="${response.server.protocolHash}"></td></tr>
35+
<tr><th scope="row">Max Players</th><td th:text="${response.server.maxPlayers}"></td></tr>
36+
<tr th:if="${response.server.formattedAddress != null}"><th scope="row">Address</th><td th:text="${response.server.formattedAddress}"></td></tr>
37+
</tbody>
38+
</table>
39+
</div>
40+
41+
<!-- Universe Data -->
42+
<div th:if="${response.universe != null}" class="mb-4">
43+
<h5>Universe</h5>
44+
<table class="table table-striped">
45+
<tbody>
46+
<tr><th scope="row">Current Players</th><td th:text="${response.universe.currentPlayers}"></td></tr>
47+
<tr th:if="${response.universe.defaultWorld != null}"><th scope="row">Default World</th><td th:text="${response.universe.defaultWorld}"></td></tr>
48+
</tbody>
49+
</table>
50+
</div>
51+
52+
<!-- Players Data -->
53+
<div th:if="${response.players != null}" class="mb-4">
54+
<h5>Players</h5>
55+
<table class="table table-striped" th:if="${!response.players.isEmpty()}">
56+
<thead>
57+
<tr>
58+
<th>Name</th>
59+
<th>UUID</th>
60+
<th>World</th>
61+
</tr>
62+
</thead>
63+
<tbody>
64+
<tr th:each="player : ${response.players}">
65+
<td th:text="${player.name}"></td>
66+
<td th:text="${player.uuid}"></td>
67+
<td th:text="${player.world}"></td>
68+
</tr>
69+
</tbody>
70+
</table>
71+
<p th:if="${response.players.isEmpty()}" class="text-muted">No players online</p>
72+
</div>
73+
74+
<!-- Plugins Data -->
75+
<div th:if="${response.plugins != null}" class="mb-4">
76+
<h5>Plugins</h5>
77+
<table class="table table-striped" th:if="${!response.plugins.isEmpty()}">
78+
<thead>
79+
<tr>
80+
<th>Identifier</th>
81+
<th>Version</th>
82+
<th>Loaded</th>
83+
<th>Enabled</th>
84+
<th>State</th>
85+
</tr>
86+
</thead>
87+
<tbody>
88+
<tr th:each="plugin : ${response.plugins}">
89+
<td th:text="${plugin.identifier}"></td>
90+
<td th:text="${plugin.version}"></td>
91+
<td th:text="${plugin.loaded}"></td>
92+
<td th:text="${plugin.enabled}"></td>
93+
<td th:text="${plugin.state}"></td>
94+
</tr>
95+
</tbody>
96+
</table>
97+
<p th:if="${response.plugins.isEmpty()}" class="text-muted">No plugins available</p>
98+
</div>
99+
</section>
7100
</body>
8101
</html>

0 commit comments

Comments
 (0)