mirror of
https://github.com/plexusorg/Module-HTTPD.git
synced 2026-06-05 17:46:53 +00:00
Redesign the HTTPD
This commit is contained in:
@@ -6,11 +6,13 @@ import dev.plex.request.AbstractServlet;
|
||||
import dev.plex.request.GetMapping;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandMap;
|
||||
@@ -18,94 +20,137 @@ import org.bukkit.command.PluginIdentifiableCommand;
|
||||
|
||||
public class CommandsEndpoint extends AbstractServlet
|
||||
{
|
||||
|
||||
private final StringBuilder list = new StringBuilder();
|
||||
private boolean loadedCommands = false;
|
||||
private String cachedHtml;
|
||||
|
||||
@GetMapping(endpoint = "/api/commands/")
|
||||
public String getCommands(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
if (!loadedCommands)
|
||||
if (cachedHtml == null)
|
||||
{
|
||||
final SortedMap<String, List<Command>> commandMap = new TreeMap<>();
|
||||
final CommandMap map = Bukkit.getCommandMap();
|
||||
for (Command command : map.getKnownCommands().values())
|
||||
{
|
||||
String plugin = "Bukkit";
|
||||
if (command instanceof PluginIdentifiableCommand)
|
||||
{
|
||||
plugin = ((PluginIdentifiableCommand) command).getPlugin().getName();
|
||||
}
|
||||
|
||||
List<Command> pluginCommands = commandMap.computeIfAbsent(plugin, k -> new ArrayList<>());
|
||||
if (!pluginCommands.contains(command))
|
||||
{
|
||||
pluginCommands.add(command);
|
||||
}
|
||||
}
|
||||
|
||||
for (String key : commandMap.keySet())
|
||||
{
|
||||
commandMap.get(key).sort(Comparator.comparing(Command::getName));
|
||||
StringBuilder rows = new StringBuilder();
|
||||
for (Command command : commandMap.get(key))
|
||||
{
|
||||
String permission = command.getPermission();
|
||||
if (command instanceof PlexCommand plexCmd)
|
||||
{
|
||||
CommandPermissions perms = plexCmd.getClass().getAnnotation(CommandPermissions.class);
|
||||
if (perms != null)
|
||||
{
|
||||
permission = (perms.permission().isBlank() ? "N/A" : perms.permission());
|
||||
}
|
||||
}
|
||||
|
||||
rows.append(createRow(command.getName(), command.getAliases(), command.getDescription(), command.getUsage(), permission));
|
||||
}
|
||||
|
||||
list.append(createTable(key, rows.toString())).append("\n");
|
||||
}
|
||||
|
||||
loadedCommands = true;
|
||||
cachedHtml = buildSections();
|
||||
}
|
||||
|
||||
return commandsHTML(list.toString());
|
||||
}
|
||||
|
||||
private String commandsHTML(String commandsList)
|
||||
{
|
||||
String file = readFile(this.getClass().getResourceAsStream("/httpd/commands.html"));
|
||||
file = file.replace("${commands}", commandsList);
|
||||
file = file.replace("${commands}", cachedHtml);
|
||||
return file;
|
||||
}
|
||||
|
||||
private String createTable(String pluginName, String commandRows)
|
||||
private static String buildSections()
|
||||
{
|
||||
return "<details id=\"" + pluginName + "\"><summary>" + pluginName + "</summary>\n"
|
||||
+ "<table id=\"" + pluginName + "Table\" class=\"table table-striped table-bordered\">\n"
|
||||
+ " <thead>\n <tr>\n <th scope=\"col\">Name (Aliases)</th>\n "
|
||||
+ "<th scope=\"col\">Description</th>\n "
|
||||
+ "<th scope=\"col\">Usage</th>\n "
|
||||
+ "<th scope=\"col\">Permission</th>\n </tr>\n</thead>\n"
|
||||
+ "<tbody>\n " + commandRows + "\n</tbody>\n</table>\n</details>";
|
||||
}
|
||||
|
||||
private String createRow(String name, List<String> aliases, String description, String usage, String permission)
|
||||
{
|
||||
return " <tr>\n <th scope=\"row\">" + name
|
||||
+ (aliases.isEmpty() || aliases.toString().equals("[]") ? "" : " (" + String.join(", ", aliases) + ")") + "</th>\n"
|
||||
+ " <th scope=\"row\">" + description + "</th>\n"
|
||||
+ " <th scope=\"row\"><code>" + cleanUsage(usage) + "</code></th>\n"
|
||||
+ " <th scope=\"row\">" + (permission != null ? permission.replaceAll(";", "<br>") : "N/A") + "</th>\n </tr>";
|
||||
}
|
||||
|
||||
private String cleanUsage(String usage)
|
||||
{
|
||||
usage = usage.replaceAll("<", "<").replaceAll(">", ">");
|
||||
if (usage.isBlank())
|
||||
final SortedMap<String, List<Command>> commandMap = new TreeMap<>();
|
||||
final CommandMap map = Bukkit.getCommandMap();
|
||||
for (Command command : map.getKnownCommands().values())
|
||||
{
|
||||
usage = "Not Provided";
|
||||
String plugin = "Bukkit";
|
||||
if (command instanceof PluginIdentifiableCommand pic)
|
||||
{
|
||||
plugin = pic.getPlugin().getName();
|
||||
}
|
||||
List<Command> pluginCommands = commandMap.computeIfAbsent(plugin, k -> new ArrayList<>());
|
||||
if (!pluginCommands.contains(command))
|
||||
{
|
||||
pluginCommands.add(command);
|
||||
}
|
||||
}
|
||||
return usage.startsWith("/") || usage.equals("Not Provided") ? usage : "/" + usage;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String key : commandMap.keySet())
|
||||
{
|
||||
List<Command> commands = commandMap.get(key);
|
||||
commands.sort(Comparator.comparing(Command::getName));
|
||||
sb.append(renderSection(key, commands));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String renderSection(String plugin, List<Command> commands)
|
||||
{
|
||||
StringBuilder cards = new StringBuilder();
|
||||
for (Command c : commands)
|
||||
{
|
||||
cards.append(renderCard(c));
|
||||
}
|
||||
String name = escapeHtml(plugin);
|
||||
return """
|
||||
<details class="command-section group mt-3 first:mt-0" data-plugin="%s" open>
|
||||
<summary class="group flex cursor-pointer list-none items-center justify-between gap-3 rounded-2xl px-2 py-3 transition-colors hover:bg-muted/40 [&::-webkit-details-marker]:hidden">
|
||||
<span class="flex items-center gap-2.5 text-lg font-medium tracking-tight">
|
||||
<svg class="size-4 text-muted-foreground transition-transform group-open:rotate-90" aria-hidden="true"><use href="#i-arrow-right"/></svg>
|
||||
%s
|
||||
</span>
|
||||
<span class="font-mono text-[11px] uppercase tracking-wider text-muted-foreground">
|
||||
%d %s
|
||||
</span>
|
||||
</summary>
|
||||
<div class="mt-3 grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
%s
|
||||
</div>
|
||||
</details>
|
||||
""".formatted(name, name, commands.size(), commands.size() == 1 ? "command" : "commands", cards);
|
||||
}
|
||||
|
||||
private static String renderCard(Command c)
|
||||
{
|
||||
String name = escapeHtml(c.getName());
|
||||
String aliases = c.getAliases() == null || c.getAliases().isEmpty() ? "" : String.join(", ", c.getAliases());
|
||||
String description = c.getDescription() == null || c.getDescription().isBlank() ? "" : escapeHtml(c.getDescription());
|
||||
String usage = cleanUsage(c.getUsage());
|
||||
String permission = resolvePermission(c);
|
||||
|
||||
String aliasMarkup = aliases.isEmpty()
|
||||
? ""
|
||||
: "<span class=\"font-mono text-xs text-muted-foreground\">/ " + escapeHtml(aliases) + "</span>";
|
||||
|
||||
String descMarkup = description.isEmpty()
|
||||
? "<p class=\"mt-2 text-sm text-muted-foreground/70 italic\">No description provided.</p>"
|
||||
: "<p class=\"mt-2 text-sm text-muted-foreground\">" + description + "</p>";
|
||||
|
||||
String searchBlob = (name + " " + aliases + " " + description + " " + permission).toLowerCase();
|
||||
|
||||
return """
|
||||
<article class="ring-card group flex flex-col rounded-2xl bg-card p-4 transition-colors hover:bg-secondary/50" data-search="%s">
|
||||
<header class="flex flex-wrap items-baseline gap-2">
|
||||
<code class="rounded-md bg-muted px-2 py-0.5 font-mono text-sm font-medium text-foreground">/%s</code>
|
||||
%s
|
||||
</header>
|
||||
%s
|
||||
<dl class="mt-3 grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1.5 border-t border-border/60 pt-3 font-mono text-[11px]">
|
||||
<dt class="text-muted-foreground uppercase tracking-wider">usage</dt>
|
||||
<dd class="text-foreground/80 break-all">%s</dd>
|
||||
<dt class="text-muted-foreground uppercase tracking-wider">perm</dt>
|
||||
<dd class="text-foreground/80 break-all">%s</dd>
|
||||
</dl>
|
||||
</article>
|
||||
""".formatted(searchBlob, name, aliasMarkup, descMarkup, usage, permission);
|
||||
}
|
||||
|
||||
private static String resolvePermission(Command c)
|
||||
{
|
||||
String permission = c.getPermission();
|
||||
if (c instanceof PlexCommand plexCmd)
|
||||
{
|
||||
CommandPermissions perms = plexCmd.getClass().getAnnotation(CommandPermissions.class);
|
||||
if (perms != null)
|
||||
{
|
||||
permission = perms.permission().isBlank() ? "N/A" : perms.permission();
|
||||
}
|
||||
}
|
||||
if (permission == null || permission.isBlank()) return "N/A";
|
||||
return escapeHtml(permission).replace(";", "<br>");
|
||||
}
|
||||
|
||||
private static String cleanUsage(String usage)
|
||||
{
|
||||
if (usage == null || usage.isBlank()) return "Not provided";
|
||||
String escaped = escapeHtml(usage);
|
||||
return escaped.startsWith("/") ? escaped : "/" + escaped;
|
||||
}
|
||||
|
||||
private static String escapeHtml(String s)
|
||||
{
|
||||
if (s == null) return "";
|
||||
return s.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user