dantevg / webstats Goto Github PK
View Code? Open in Web Editor NEWSpigot plugin to display the scoreboard, PlaceholderAPI and other plugin statistics on the web
Home Page: https://dantevg.nl/mods-plugins/WebStats
License: MIT License
Spigot plugin to display the scoreboard, PlaceholderAPI and other plugin statistics on the web
Home Page: https://dantevg.nl/mods-plugins/WebStats
License: MIT License
Instead of just showing nothing when something goes wrong, show an error message in place of the table. Also add a message "Loading..." before any data is loaded.
can i convert offline uuid from uuid column to nickname in MYCUSTOMTABLE table? I configured it like this but nothing worked
database:
hostname: localhost
username: DATABASE USERNAME
password: DATABASE PASSWORD
config:
- database: DATABASE NAME
table: MYCUSTOMTABLE
convert: # Some examples, refer to documentation for explanations
- [uuid, column]
Java Players are availabale, but Bedrock (GeyserMC) players aren't listed
the 'web' folder is empty for me. getting 'No connection to server. Maybe the server is offline, or the 'host' setting in index.html is incorrect.'
(from Unkn0wn3636 at the spigotmc WebStats thread)
For non-player-specific placeholders, display them on a separate row for the server itself, instead of displaying them for every player.
Maybe add a separate global-placeholders
config entry. All placeholders specified there will not be displayed on individual players' rows but on the server's own row.
Add option to serve the webpage from the plugin (like dynmap), so no external web server is required. Meant for people that do not have a web server set up.
enable-internal-webserver
With this, one can make a nice automatically-updating and interactive graph of players' playtime for instance.
WebStats does not adjust to rate limiting at all. This results in getting a HTTP 429 response from Discord with this body:
{"code": 0, "message": "You are being blocked from accessing our API temporarily due to exceeding our rate limits frequently. Please read our docs at https://discord.com/developers/docs/topics/rate-limits to prevent this moving forward."}
TODO
Specify a custom order for the columns, in config.yml
:
columns: [player, money, deaths]
For columns not in this list, either:
(came from #5 (comment))
It would be nice if it's able to show top players (for example, top 100) in a selected stat.
Perhaps they can be placed in multiple sheets.
Btw, https can be achieved by reverse proxy.
Since some PlaceholderAPI plugins do not return placeholder data for offline players, store the data for them when they are online so we can use that stored data when they're offline.
(came from #5 (comment))
When I apply any filter in the latest build it for some reason like resets the filter and goes back to the default view after just a few moments. I assumed it was the update interval causing it but even after disabling auto updates it still seems to remove the filters on me.
I am using the default web page you made with the default index/styles.
Example below.
s1stats.mineroyalemc.com
Command to reload config without needing to restart the entire server and without using the server's unsupported /reload
command
Instead of identifying them by their display name, allow using the internal name. This prevents confusion when two objectives have the same display name.
URL: http://lcorpnet.ddns.net:8094/
Keeping this up so it can be checked to see the result
Config: https://pastebin.com/ckET1DVj
Mostly just added a bunch of placeholders for testing
Doesn't seem to initialize the table. works on a localhost test server fine. when running from a hosted server (Pebblehost) I get a similar result to this but no error in console? Stats.json IS propagated from checking.
#16
Paper: 1.18.2
I'd be happy to offer other info if needed
When changing the config and removing placeholders, the placeholder storage table still contains entries for these placeholders.
When storing all placeholders into the database, remove any rows that have a placeholder name that is not known.
Folia is a new fork of Paper centred around multithreading, and it seems to still be at an experimental stage right now. Once plugins that WebStats depends on support it, WebStats should also have support for Folia.
However, support for Folia should not come at the expense of support for Paper, as I expect the grand majority of servers will stay running Paper.
Thanks to radeon on Discord for the suggestion!
ALL scoreboard API is considered broken (this is global state that I've not figured out how to properly implement yet)
folia-supported: true
to plugin.ymlAdd functionality from https://github.com/Dantevg/WebStatsStorer into the main plugin.
To reduce file size, also only store a statistic for a player if it actually changed.
Maybe only request a statistic update when people are online?
Sometimes randomly the web frontend stops working and stopping the server hangs on "Disabling WebStats". Score gathering still works (WebStatsStorer still saved new stats)
Output of /webstats debug
command:
WebStats 1.6.1
Active sources: scoreboard
Thread status: alive
Socket status: open
Something in ServerSocket
or HTTPConnection
hangs
Look from here where something can hang indefinitely:
Additional solution: don't wait indefinitely on thread join here: (timeout of a couple seconds should be good)
Also maybe use Thread::getState
in debug output
hey
Is it possible to add Placeholders compatibility? for example add faction power to the table, or mcmmo level, by adding %placeholder% to a .yml config
Can use EssentialsX API to detect whether a player is vanished.
Thanks to Just4Fun on the Spigotmc forums for the suggestion!
(from Unkn0wn3636 at the spigotmc WebStats thread)
Minecraft version: 1.18.2
Stack trace:
[11:32:01 ERROR]: Error occurred while disabling WebStats v1.6.1 (Is it up to date?)
java.lang.IllegalStateException: zip file closed
at java.util.zip.ZipFile.ensureOpen(ZipFile.java:831) ~[?:?]
at java.util.zip.ZipFile.getEntry(ZipFile.java:330) ~[?:?]
at java.util.jar.JarFile.getEntry(JarFile.java:518) ~[?:?]
at java.util.jar.JarFile.getJarEntry(JarFile.java:473) ~[?:?]
at org.bukkit.plugin.java.PluginClassLoader.findClass(PluginClassLoader.java:163) ~[paper-api-1.18.2-R0.1-SNAPSHOT.jar:?]
at java.lang.ClassLoader.loadClass(ClassLoader.java:587) ~[?:?]
at org.bukkit.plugin.java.PluginClassLoader.loadClass0(PluginClassLoader.java:108) ~[paper-api-1.18.2-R0.1-SNAPSHOT.jar:?]
at org.bukkit.plugin.java.PluginClassLoader.loadClass(PluginClassLoader.java:103) ~[paper-api-1.18.2-R0.1-SNAPSHOT.jar:?]
at java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[?:?]
at me.vagdedes.ultimatestatistics.b.b.b.onRequest(PlaceHolder.java:46) ~[UltimateStatistics.jar:?]
at me.clip.placeholderapi.replacer.CharsReplacer.apply(CharsReplacer.java:161) ~[PlaceholderAPI-2.11.1.jar:?]
at me.clip.placeholderapi.PlaceholderAPI.setPlaceholders(PlaceholderAPI.java:70) ~[PlaceholderAPI-2.11.1.jar:?]
at nl.dantevg.webstats.placeholder.PlaceholderSource.lambda$getScoresForPlayer$1(PlaceholderSource.java:93) ~[WebStats-1.7.0-pre1.jar:?]
at java.util.LinkedHashMap.forEach(LinkedHashMap.java:721) ~[?:?]
at nl.dantevg.webstats.placeholder.PlaceholderSource.getScoresForPlayer(PlaceholderSource.java:92) ~[WebStats-1.7.0-pre1.jar:?]
at nl.dantevg.webstats.placeholder.PlaceholderStorage.save(PlaceholderStorage.java:121) ~[WebStats-1.7.0-pre1.jar:?]
at nl.dantevg.webstats.placeholder.PlaceholderStorage.saveAll(PlaceholderStorage.java:154) ~[WebStats-1.7.0-pre1.jar:?]
at nl.dantevg.webstats.placeholder.PlaceholderStorage.disconnect(PlaceholderStorage.java:59) ~[WebStats-1.7.0-pre1.jar:?]
at nl.dantevg.webstats.placeholder.PlaceholderSource.disable(PlaceholderSource.java:105) ~[WebStats-1.7.0-pre1.jar:?]
at nl.dantevg.webstats.WebStats.onDisable(WebStats.java:99) ~[WebStats-1.7.0-pre1.jar:?]
at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:266) ~[paper-api-1.18.2-R0.1-SNAPSHOT.jar:?]
at org.bukkit.plugin.java.JavaPluginLoader.disablePlugin(JavaPluginLoader.java:399) ~[paper-api-1.18.2-R0.1-SNAPSHOT.jar:?]
at org.bukkit.plugin.SimplePluginManager.disablePlugin(SimplePluginManager.java:538) ~[paper-api-1.18.2-R0.1-SNAPSHOT.jar:?]
at org.bukkit.plugin.SimplePluginManager.disablePlugins(SimplePluginManager.java:515) ~[paper-api-1.18.2-R0.1-SNAPSHOT.jar:?]
at org.bukkit.craftbukkit.v1_18_R2.CraftServer.disablePlugins(CraftServer.java:493) ~[paper-1.18.2.jar:git-Paper-235]
at net.minecraft.server.MinecraftServer.stopServer(MinecraftServer.java:973) ~[paper-1.18.2.jar:git-Paper-235]
at net.minecraft.server.dedicated.DedicatedServer.stopServer(DedicatedServer.java:803) ~[paper-1.18.2.jar:git-Paper-235]
at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1272) ~[paper-1.18.2.jar:git-Paper-235]
at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:317) ~[paper-1.18.2.jar:git-Paper-235]
at java.lang.Thread.run(Thread.java:833) ~[?:?]
Using TypeScript could catch some errors earlier, but it might not be useful
This server is running Purpur version git-Purpur-1449 (MC: 1.18.1) (Implementing API version 1.18.1-R0.1-SNAPSHOT) (Git: 91e3be5 on HEAD)
There's nothing on the page except:
Compact Hide offline players Prev Next
An error appears in the console:
[20:18:56 WARN]: Exception in thread "WebStats" java.lang.IllegalStateException: @NotNull method nl/dantevg/webstats/placeholder/PlaceholderStorer.getScore must not return null
[20:18:56 WARN]: at WebStats-1.6.0.jar//nl.dantevg.webstats.placeholder.PlaceholderStorer.$$$reportNull$$$0(PlaceholderStorer.java)
[20:18:56 WARN]: at WebStats-1.6.0.jar//nl.dantevg.webstats.placeholder.PlaceholderStorer.getScore(PlaceholderStorer.java:158)
[20:18:56 WARN]: at WebStats-1.6.0.jar//nl.dantevg.webstats.placeholder.PlaceholderSource.lambda$getScores$0(PlaceholderSource.java:64)
[20:18:56 WARN]: at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:721)
[20:18:56 WARN]: at WebStats-1.6.0.jar//nl.dantevg.webstats.placeholder.PlaceholderSource.getScores(PlaceholderSource.java:56)
[20:18:56 WARN]: at WebStats-1.6.0.jar//nl.dantevg.webstats.placeholder.PlaceholderSource.getStats(PlaceholderSource.java:90)
[20:18:56 WARN]: at WebStats-1.6.0.jar//nl.dantevg.webstats.Stats.getStats(Stats.java:23)
[20:18:56 WARN]: at WebStats-1.6.0.jar//nl.dantevg.webstats.Stats.getAll(Stats.java:34)
[20:18:56 WARN]: at WebStats-1.6.0.jar//nl.dantevg.webstats.HTTPConnection.route(HTTPConnection.java:50)
[20:18:56 WARN]: at WebStats-1.6.0.jar//nl.dantevg.webstats.HTTPConnection.start(HTTPConnection.java:25)
[20:18:56 WARN]: at WebStats-1.6.0.jar//nl.dantevg.webstats.WebStats.run(WebStats.java:115)
[20:18:56 WARN]: at java.base/java.lang.Thread.run(Thread.java:833)
The plugin gets the information and writes it to SQL
webstats debug
[20:24:11 INFO]: WebStats 1.6.0
Active sources: scoreboard, PlaceholderAPI
Thread status: dead
Socket status: open
Placeholder storer database connection: connected
Loaded placeholders:
81ada1cc-0334-4d7d-8806-0e8e78cc2a7f (Fandorin): balance = 823.3333333333333
81ada1cc-0334-4d7d-8806-0e8e78cc2a7f (Fandorin): displayed name = Fandorin
81ada1cc-0334-4d7d-8806-0e8e78cc2a7f (Fandorin): uuid = 81ada1cc-0334-4d7d-8806-0e8e78cc2a7f
Config:
port: 8989
columns: [uuid, balance]
objectives:
Some values have large numbers and it can be hard to see what they mean. The option to specify per column what the unit of the values is would solve this. Possible units / categories:
12:34:56.789
)1st 23
)123m
, 12.34km
)Example config: (minutes
, ticks
, items
, timestamp
are taken as the source units, WebStats will choose best units to display)
column-units:
Play Minutes: minutes
Aviate: ticks
Mine Diamond: items
Last Seen: timestamp
Possible extra function: show original value on hover ("tooltip")
Add an arrow in the currently sorted column, indicating the sorting direction (ascending/descending).
The arrow can probably be an inline svg triangle.
Make the webpage more accessible:
tabindex=0
to table column headings(from Unkn0wn3636 at the spigotmc WebStats thread)
Add possibility to display multiple tables with different scores, on different pages.
tables
: list or named list, each item of this list is a list of columns. Mutually exclusive with columns
of course.stats.json?page=1
or stats.json?page=pagename
Hello, your guide is a bit confusing, could you make a video version or something like that?
(from draexo at the spigotmc WebStats thread)
Discord webhook that posts a (non-interactive) embed message with the top 10 (or so) sorted on a specified stat. Auto-update every minute (or so).
Probably use inline fields. This has a problem: if there are too many columns or player names are too long, overflowing columns get put on a new row, breaking the visual connection to the player. Alternative is to use code blocks for monospaced manual table display.
The ability to store the placeholders for offline players in a plain text file instead of requiring a database
ip-to-names.yml
(#19), orConfig option boolean store-placeholders-in-file
, mutually exclusive to store-placeholders-database
.
Otherwise, maybe change config to:
store-placeholders
method
(database
or file
)database-name
Add a "bar graph" to each cell: a horizontal filled bar behind each cell, scaled by the relative score of that player for that objective. This will make it easier to compare scores at a glance, rather than having to read the numbers: 6,241 does not look a lot more than 2,243 at first, yet it is.
Just creating this as a separate issue to keep track.
If all stats for a user is 0, the stats table does not load at all, and errors out with a "Cannot read property of undefined" or something similar (the error disppeared as soon as one of the scoreboard-values was updated to 1, so I don't have the exact error message in frnt of me anymore).
If just some values are 0, the table looks a bit strange as 0-values are not shown:
Support serving the stats over https natively, somehow. Without relying on manual configuration.
For the internal web server, this is not needed because everything is unencrypted. For an external web server this is also not needed because you can set up a reverse proxy. Native HTTPS support is only needed for external websites created with services like Wix, Squarespace or Weebly, where you cannot set up a reverse proxy.
It is possible (and easy) to generate a self-signed certificate and use that. But, browsers will give you a big fat warning.
So, we need to somehow get a validated certificate from something like letsencrypt. The problem is that they use DV (domain validation), which seems to not be available for IPs, only domains.
Challenge types: https://letsencrypt.org/docs/challenge-types/
The HTTP-01 and TLS-ALPN-01 challenge types use port 80 or 443 to verify the certificate, which you do not have access to unless you self-host.
We can use the DNS-01 challenge in manual mode. Downside: need to manually renew the certificate every few months.
How to get a letsencrypt certificate with DNS validation: [IN PROGRESS]
https://eff-certbot.readthedocs.io/en/stable/using.html#manual
sudo certbot certonly --manual --preferred-challenges dns
sudo openssl pkcs12 -export -out webstats.p12 -in /etc/letsencrypt/live/tlstest.equinoxmc.nl/fullchain.pem -inkey /etc/letsencrypt/live/tlstest.equinoxmc.nl/privkey.pem -name webstats
sudo keytool -importkeystore -srckeystore webstats.p12 -srcstoretype pkcs12 -srcalias webstats -destke
ystore webstats.jks -deststoretype jks -deststorepass webstats -destalias webstats
DuckDNS is a free DNS provider with an API to change A and TXT records. This means you can use a DuckDNS subdomain as the domain for the WebStats plugin on your server.
DNS TXT record URL format: (https://www.duckdns.org/spec.jsp)
https://www.duckdns.org/update?domains=<SUBDOMAIN>.duckdns.org&token=<DUCKDNS_TOKEN>&txt=<DNS_CHALLENGE>
To automatically renew the certificate, the plugin needs to use a Java library. Acme4J is listed in the letsencrypt list of Java libraries. [IN PROGRESS]
ZeroSSL seems to be an alternative to letsencrypt that does support TLS certificates for IP addresses: https://zerossl.com/documentation/api/create-certificate/. This still does not work because we would need to get a certificate for only the WebStats port.
Every time I initialize the server after a few seconds it isn't accessible anymore. If I reload the plugin it comes back but then it turns off again.
Hi, I am trying to understand how to setup this exactly,
The help to setup isnt easy for me to understand (Prolly just me), but I cant seem to understand how to add in multiple columns and link them to their correct placeholder/scoreboard objective.
And as for a webpage, I do not quite understand how to set it up correctly :/
Is there an example of any of these that can help me understand it all better?
A source map is already generated, but the plugin does not serve it. Letting the plugin serve the source map allows for better debugging and minimisation of WebStats-dist.js
The plugin should reconnect automatically after the connection is closed.
https://pastebin.com/eN1LkPM8
An option to only display players that are online, with a button/checkbox to toggle
(came from #5 (comment))
It could just be me, but I can't get the column-ordering to work.
I have changed config.yml and added:
tables:
#columns: []
columns: ['Level', 'XP', 'Total Kills', 'Death Count']
These are the display names, but I have also tried the objective name (if I understand this correctly). E.g:
> scoreboard objectives add deathCount deathCount "Death Count"
And then use deathCount
in the config:
columns: ['level', 'XP', 'totalKills', 'deathCount']
But it always displays them in alphabetical order:
Another little issue there is that it would be nice if empty columns could show up. It was very confusing when I started setting up this plugin that I did not see anything.
hey there, again.
Some improvements! congratulations!
Here is some of my doubts:
Hey, would you ever consider making this MySQL compatible? I love the idea here, but I don't use the Scoreboard and store all my Data in MySQL.
Like Dynmap can show player names for messages sent from the browser, WebStats should be able to highlight the row for your player.
Probably by storing players' IPs when they are online and matching that against the request IP.
Since this is my only way to contact you, can you help me with the implementation?
We have our server (purpur) and website (laravel on xampp) running on a laptop but im kinda noob with this stuff.
Is the following code right? https://pastebin.com/tQcApvkK
From console:
Nag author(s): '[RedPolygon]' of 'WebStats' about their usage of System.out/err.print. Please use your plugin's logger instead (JavaPlugin#getLogger).
Happens when closing WebStats browser tab.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.