73 Commits

Author SHA1 Message Date
sfan5
fda88af676 Replace outdated URLs 2025-10-16 16:20:11 +02:00
sfan5
2f66e1deca Fix logic error in server duplicate check 2025-10-16 13:45:09 +02:00
sfan5
258add93e0 Fix average client calculation 2025-02-17 19:55:30 +01:00
sfan5
de30a4e1ac Limit updates of list to disk to every 5s 2025-02-17 19:38:27 +01:00
sfan5
b93b50ad11 Move sorting into save operation 2025-02-17 19:38:27 +01:00
sfan5
431ac110c7 Adjust legacy support penalty 2025-02-17 19:38:27 +01:00
sfan5
aedabc50a8 Logically deduplicate servers on list 2025-02-17 19:38:27 +01:00
sfan5
6df3b93f48 Encapsulate server data into class, split persistency 2025-02-17 19:38:27 +01:00
sfan5
f71be0af67 Drop intended verification changes
As discussed internally there are some edge cases where this
would require unreasonable changes on the server owner's part
2025-02-17 19:38:27 +01:00
sfan5
cd0f2a56d0 Add editorconfig 2025-02-17 19:38:27 +01:00
sfan5
4d320bbcf5 Set short max_age for list.json 2024-11-03 12:42:37 +01:00
sfan5
5d191896f3 Some documentation adjustments 2024-11-03 12:38:50 +01:00
sfan5
77951100b9 Apply Luanti rename 2024-11-02 18:25:05 +01:00
sfan5
6edaa91315 Rework domain verification a bit (#67)
Co-authored-by: ShadowNinja <ShadowNinja@users.noreply.github.com>
2024-10-22 22:53:17 +02:00
sfan5
a8a9d92077 Fix some complaints from pylint 2024-10-14 23:54:06 +02:00
sfan5
3d08cd4ff4 Report delayed errors (#66) 2024-10-14 22:39:09 +02:00
sfan5
dcc8d5ec74 Get rid of uptime tracking and point penalty
closes #37
2024-10-05 15:22:54 +02:00
sfan5
df032cb47c Some adjustments to script and template 2024-10-04 11:32:23 +02:00
sfan5
967c1a0b51 Apply modern-normalize CSS 2024-10-04 11:26:27 +02:00
sfan5
4533842e41 Add contact and privacy links 2024-10-04 10:44:55 +02:00
sfan5
1f1af8828c Check request data more carefully 2024-07-08 19:51:55 +02:00
sfan5
4584459fca Don't penalize no clients_list 2024-07-08 18:53:26 +02:00
sfan5
85aff93b02 Fix templating instructions 2024-03-28 16:03:12 +01:00
ROllerozxa
17b52cd647 Subgame -> Game in serverlist frontend 2024-03-28 15:55:04 +01:00
sfan5
78e6c48c85 Drop support for announce via GET 2024-03-06 23:07:08 +01:00
sfan5
cb8fa58df4 Sanity check server addresses against common mistakes 2024-03-03 23:57:09 +01:00
sfan5
c02ed9f07a Fix serverUp error handling 2024-03-03 23:57:09 +01:00
sfan5
d945b26f9f Add minimal lint workflow 2024-03-02 18:55:10 +01:00
sfan5
12ed8aff60 Use 'sans-serif' in font CSS
sans is a non-portable alias for sans-serif
2023-07-09 15:52:47 +02:00
Buckaroo Banzai
6fba704bb0 use max_age instead of cache_timeout (for flask > 2.0) 2023-05-05 10:36:08 +02:00
sfan5
218f8d9bc5 Fix cache control for geoip response
Storing this in public caches is not actually safe.
2022-09-20 22:49:13 +02:00
sfan5
b0fed2e9f7 Switch GeoIP backend to be able to use an up-to-date database 2022-09-20 22:23:11 +02:00
sofar
d2f09370f7 Add interface for client to lookup it's own continent (#34)
This adds `/geoip` which returns a json object with one key-value pair,
`continent` = `unknown` or one of `NA`, `SA` etc. plus a 200 exit code,
or a 500 code if things went south badly.
2022-09-20 21:53:04 +02:00
sfan5
84a40e406b Remove "guest" user penalty
Much overdue as the mobile apps that justified this are long gone
closes #50, closes #17
2021-09-05 21:06:57 +02:00
sfan5
2f4ffde916 Recalculate stats with protocol filter applied
closes #49
2021-09-05 20:47:37 +02:00
sfan5
5d5f31d295 Only apply uptime penalty on repeated restarts 2021-03-15 12:35:02 +01:00
sfan5
a9ecf55b38 Avoid unnecesary disk writes 2021-03-15 11:38:49 +01:00
sfan5
9f144f3e3c Adjust server ranking
specificially, penalize servers that support both v4 and v5
Reasoning: We, as a project, have no interest in promoting servers
that intentionally restrict themselves to the feature set of an old,
potentially buggy version.
2021-03-15 11:27:38 +01:00
sfan5
e37149a834 Improve table column sizing, template, CSS 2021-02-21 14:08:00 +01:00
luk3yx
a5bc675a6e Replace "master server" with "serverlist" in README.md (#43) 2020-12-30 16:27:11 +01:00
Tyler Schwend
578a7bc987 doc: Support use of Apache (#40) 2020-12-28 20:06:19 +01:00
sfan5
e99ecd6582 Improve behaviour of protocol filtering 2020-03-08 12:20:19 +01:00
sfan5
ddcd98a457 Highlight even table rows better 2020-02-20 20:04:40 +01:00
sfan5
56ece3ba3d Clean up template a little 2019-08-13 13:44:47 +02:00
sfan5
ba0077a4f5 Add dropdown that filters by protocol version 2019-08-13 13:21:04 +02:00
sfan5
04810a094c Improve lookup failure logging 2019-07-17 12:53:11 +02:00
sfan5
85c3048cd4 Fix handling of GeoIP lookup failure 2019-07-17 12:46:57 +02:00
Auke Kok
772fc29cb8 Detect geo of server.
Using geolite2, we attempt to detect the continent of the server
and store the 'continent code' in the serverlist. This is reasonably
broad enough to help players in the client find truly "nearby" server.

The client will have to be changed to select a continent to change
ordering or filtering.
2019-07-17 12:33:48 +02:00
sfan5
ac66259801 Less confusing status codes 2019-07-17 12:23:35 +02:00
sfan5
794807c9ff Raise JSON length limit
it's not hard to hit 5000 if you just throw in a lot of mods
2019-05-07 17:30:15 +02:00
sfan5
8aa2efd5eb Move some styles back into index.html
These should only apply on servers.minetest.net, not when
the server list is embedded into another page.
2019-02-13 13:28:32 +01:00
luk3yx
8d0c99b5d0 Add nicer CSS
• Add more whitespace around everything.
• Change the fonts.
• Change the colours/colors.
• Add a shadow around the mods/flags/etc lists.
2019-02-13 13:24:06 +01:00
sfan5
6f51e2f00f Tune server ranking (again) 2018-11-11 23:16:43 +01:00
sfan5
e7c4d2c20a Tune server ranking 2018-10-27 22:46:12 +02:00
sfan5
67d8515fd8 Move misplaced <script> tag in index.html 2018-10-27 22:16:58 +02:00
sfan5
57fb13cbb8 Use most compact JSON representation 2018-06-25 13:19:49 +02:00
sfan5
0a3d05baf5 Use portable os.replace instead of relying on POSIX behaviour 2018-06-25 12:48:23 +02:00
nOOb3167
a2b47ff52b Document serverUp protocol
serverUp uses hex spaghetti to send and receive a short sequence of packets, to see whether a server is online.
The packet format is now annotated for easier cross-reference with minetest source.
2018-06-04 16:27:08 +02:00
nOOb3167
e49da8f1b9 Compatibility fix (string field conversion) 2018-05-18 10:09:36 +02:00
nOOb3167
48020105af Schedule purging using plain python, obviating the need for APScheduler 2018-05-18 10:05:57 +02:00
nOOb3167
f43f201af5 Fix potential use-before-defined of serverList variable 2018-05-18 10:05:57 +02:00
nOOb3167
23d45c0a15 Improve use of os.path.join 2018-03-18 14:07:59 +01:00
nOOb3167
f5bddaaef5 Fix locking
Calls to save() in purgeOld() and update() were in race condition.
Race condition was eliminated by extending the lock scope within purgeOld().
Calls to load() were in race condition with themselves.
While current use of load() (called only during construction of class ServerList()) does not manifest the bug, any future change introducing concurrent use of load() would.
Race condition potential was eliminated by extending the lock scope within load().
2018-03-18 14:07:59 +01:00
nOOb3167
78abbee771 Fix server purging 2018-03-18 14:07:59 +01:00
sfan5
2f87286475 Make hostname blacklist case insensitive 2017-12-24 20:42:47 +01:00
sfan5
0d93321f6d Restrict protocol of "server_url" values to HTTP(S) 2017-11-06 19:45:28 +01:00
sfan5
da9f297346 Refine "guest" name regex
Avoid detecting names such as "Bob123" (<4 chars) or "Angel060" (leading zero)
2017-08-08 23:30:35 +02:00
sfan5
afd7b16e5b README formatting fixes 2017-06-14 13:37:45 +02:00
sfan5
828a1fda7e Allow banning by server hostname 2017-05-14 15:29:46 +02:00
sfan5
705ea6e1a0 Re-add banlist features
This effectively reverts commit b366290118.
2017-05-14 15:03:05 +02:00
sfan5
5de6082f57 Design changes on the server list page 2017-02-18 20:34:53 +01:00
sfan5
5e12cb5022 Update detection of guest clients 2017-02-18 11:31:24 +01:00
ShadowNinja
58f03d0395 Try to fix list corruption issue
Sometimes the list was send in a corrupted form.  This
appeared to be the result of overwriting the file as it
was being read by the server.  This commit tries to fix
this by saving to a temporary file and then moving it over
the served file.
2016-02-04 16:49:25 -05:00
12 changed files with 955 additions and 423 deletions

8
.editorconfig Normal file
View File

@@ -0,0 +1,8 @@
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true

28
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: lint
on:
push:
paths:
- '**.py'
- 'requirements.txt'
pull_request:
paths:
- '**.py'
- 'requirements.txt'
jobs:
pylint:
runs-on: ubuntu-latest
container:
image: python:3.11-slim
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
pip install -r requirements.txt
pip install pylint
- name: Lint
run: |
pylint -E --fail-on=E server.py

9
.gitignore vendored
View File

@@ -1,7 +1,8 @@
*~
node_modules
__pycache__
static/list.json
static/servers.js
config.py
/store.json
/static/list.json
/static/servers.js
/config.py
/*.mmdb

168
README.md
View File

@@ -1,4 +1,4 @@
Minetest server list
Luanti server list
====================
Setting up the webpage
@@ -9,101 +9,141 @@ the server list webpage template.
First install node.js, e.g.:
# apt-get install nodejs
# # OR:
# pacman -S nodejs
# # OR:
# emerge nodejs
```sh
apt-get install nodejs
# OR:
yum install nodejs
```
Then install doT.js and its dependencies:
$ cd ~
$ npm install dot commander mkdirp
```sh
npm install dot "commander@11.1.0" mkdirp
```
And finally compile the template:
$ cd static
$ ~/node_modules/dot/bin/dot-packer -s . -d .
```sh
cd static
../node_modules/dot/bin/dot-packer -s .
```
You can now serve the webpage by copying the files in static/ to your web root, or by [starting the master server](#setting-up-the-server).
You can now serve the webpage by copying the files in `static/` to your web root, or by [starting the server list](#setting-up-the-server).
Embedding the server list in a page
-----------------------------------
<head>
...
<script>
var master = {
root: 'http://servers.minetest.net/',
limit: 10,
clients_min: 1,
no_flags: 1,
no_ping: 1,
no_uptime: 1
};
</script>
...
</head>
<body>
...
<div id="server_list"></div>
...
</body>
<script src="list.js"></script>
```html
<head>
...
<script>
var master = {
root: 'https://servers.luanti.org/',
limit: 10,
clients_min: 1,
no_flags: true,
no_ping: true,
no_uptime: true
};
</script>
...
</head>
<body>
...
<div id="server_list"></div>
...
<script defer src="https://servers.luanti.org/list.js"></script>
</body>
```
Setting up the server
---------------------
1. Install Python 3 and pip:
# pacman -S python python-pip
# # OR:
# apt-get install python3 python3-pip
```sh
apt-get install python3 python3-pip
# OR:
yum install python3 python3-pip
```
2. Install required Python packages:
# # You might have to use pip3 if your system defaults to Python 2
# pip install -r requirements.txt
pip3 install -r requirements.txt
3. If using in production, install uwsgi and it's python plugin:
3. If using in production, install uwsgi and its python plugin:
# pacman -S uwsgi uwsgi-plugin-python
# # OR:
# apt-get install uwsgi uwsgi-plugin-python
# # OR:
# pip install uwsgi
```sh
apt-get install uwsgi-plugin-python3
# OR:
yum install uwsgi uwsgi-plugin-python3
```
4. Install, start, and enable MongoDB on boot:
4. Configure the server by adding options to `config.py`.
See `config-example.py` for defaults.
# pacman -S mongodb && systemctl enable mongodb --now
5. Start the server:
5. Configure the server by adding options to `config.py`.
See `config-example.py` for defaults.
6. Start the server:
$ ./server.py
$ # Or for production:
$ uwsgi -s /tmp/minetest-master.sock --plugin python -w server:app --enable-threads
$ # Then configure according to http://flask.pocoo.org/docs/deploying/uwsgi/
```sh
./server.py
# Or for production:
uwsgi -s /run/serverlist.sock --plugins python3 -w server:app -T --threads 2
# then configure according to https://flask.palletsprojects.com/en/stable/deploying/uwsgi/
```
7. (optional) Configure the proxy server, if any. You should make the server
load static files directly from the static directory. Also, `/list`
should be served from `list.json`. Example for nginx:
load static files directly from the static directory. Also, `/list`
should be served from `list.json`. Example for nginx:
root /path/to/server/static;
rewrite ^/list$ /list.json;
try_files $uri @uwsgi;
location @uwsgi {
uwsgi_pass ...;
}
```sh
root /path/to/server/static;
rewrite ^/$ /index.html break;
rewrite ^/list$ /list.json break;
location = /list.json { expires 20s; }
try_files $uri @uwsgi;
location @uwsgi {
include uwsgi_params;
uwsgi_pass unix:/run/serverlist.sock;
}
```
Setting up the server (Apache version)
--------------------------------------
If you wish to use Apache to host the server list, do steps 1-2, 4, above.
Additionally install/enable mod_wsgi and an Apache site config like the following:
```sh
# This config assumes you have the server list at DocumentRoot.
# Visitors to the server list in this config would visit http://local.server/ and
# apache would serve up the output from server.py.
# Where are the serverlist files located?
DocumentRoot /var/games/luanti/serverlist
# Serve up server.py at the root of the URL.
WSGIScriptAlias / /var/games/luanti/serverlist/server.py
# The name of the function that we call when we invoke server.py
WSGICallableObject app
# These options are necessary to enable Daemon mode. Without this, you'll have strange behavior
# with servers dropping off your list! You can tweak threads as needed. See mod_wsgi documentation.
WSGIProcessGroup luanti-serverlist
WSGIDaemonProcess luanti-serverlist threads=2
<Directory /var/games/luanti/serverlist>
Require all granted
</Directory>
```
License
-------
The Minetest master server is licensed under the GNU Lesser General Public
The Luanti server list code is licensed under the GNU Lesser General Public
License version 2.1 or later (LGPLv2.1+). A LICENSE.txt file should have been
supplied with your copy of this software containing a copy of the license.

View File

@@ -1,5 +1,3 @@
from datetime import timedelta
# Enables detailed tracebacks and an interactive Python console on errors.
# Never use in production!
DEBUG = False
@@ -9,23 +7,24 @@ HOST = "127.0.0.1"
# Port for development server to listen on
PORT = 5000
# Amount of time after which servers are removed from the list if they haven't
# updated their listings. Note: By default Minetest servers only announce
# once every 5 minutes, so this should be more than that.
UPDATE_TIME = timedelta(minutes=6)
# Amount of time, is seconds, after which servers are removed from the list
# if they haven't updated their listings. Note: By default Luanti servers
# only announce once every 5 minutes, so this should be more than 300.
PURGE_TIME = 350
# Amount of time after which servers are removed from the database if they
# haven't updated their listings.
PURGE_TIME = timedelta(days=30)
# List of banned IP addresses for announce
# e.g. ['2620:101::44']
BANNED_IPS = []
# List of banned servers as host/port pairs
# e.g. ['1.2.3.4/30000', 'lowercase.hostname', 'lowercase.hostname/30001']
BANNED_SERVERS = []
# Creates server entries if a server sends an 'update' and there is no entry yet.
# This should only be used to populate the server list after list.json was deleted.
# This WILL cause problems such as mapgen, mods and privilege information missing from the list
ALLOW_UPDATE_WITHOUT_OLD = False
# Number of days' data to factor into popularity calculation
POP_DAYS = 3
# Address of the MongoDB server. You can use domain sockets on unix.
MONGO_URI = "mongodb://localhost/minetest-master"
# Reject servers with private addresses and domain names.
# Enable this if you are running a list on the public internet.
REJECT_PRIVATE_ADDRESSES = False

View File

@@ -1,4 +1,2 @@
APScheduler>=3
Flask>=0.10
Flask-PyMongo>=0.3
Flask>=2.0.0
maxminddb>=2.0.0

847
server.py

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,36 @@
<html>
<head>
<meta charset="utf-8">
<title>Minetest server list</title>
<title>Luanti server list</title>
<link rel="stylesheet" href="modern-normalize.min.css">
<style>
body {
margin: .5em;
}
a {
color: #336B87;
}
a:visited {
color: #336BA1;
}
hr {
border: 0;
border-top: 3px solid #53ac56;
}
@media only screen and (max-width: 1024px) {
#server_list table .version, #server_list table .flags, #server_list table .uptime {
display: none;
}
}
</style>
<script>
var master = {show_proto_select: true};
</script>
</head>
<body>
<span class="h"><strong>Luanti server list</strong> | <a href="https://www.luanti.org/get-involved/#reporting-issues">Contact</a> | <a href="https://www.luanti.org/app-privacy-policy/">Privacy</a></span>
<hr />
<div id="server_list"></div>
<script src="list.js"></script>
</body>
</html>
<script src="list.js"></script>

View File

@@ -1,101 +1,146 @@
var master;
if (!master) master = {};
if (typeof(master.root) == 'undefined') master.root = window.location.href;
if (!master.output) master.output = '#server_list';
if (!master.list) master.list = "list";
if (!master.list_root) master.list_root = master.root;
if (!master.list_url) master.list_url = master.list_root + master.list;
if (!master)
master = {};
if (!master.root)
master.root = window.location.href;
if (!master.list)
master.list = "list";
if (!master.list_root)
master.list_root = master.root;
if (!master.list_url)
master.list_url = master.list_root + master.list;
master.cached_json = null;
// Utility functions used by the templating code
function humanTime(seconds) {
if (typeof(seconds) != "number") return '?';
if (typeof(seconds) != "number")
return '?';
var conv = {
y: 31536000,
d: 86400,
h: 3600,
m: 60
}
};
for (var i in conv) {
if (seconds >= conv[i]) {
return (seconds / conv[i]).toFixed(1) + i;
return (seconds / conv[i]).toFixed(i=='y'?1:0) + i;
}
}
return seconds + 's';
}
function escapeHTML(str) {
if(!str) return str;
if (!str)
return str;
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
function addressString(server) {
var isIPv6 = server.address.indexOf(":") != -1;
var addrStr = (isIPv6 ? '[' : '') +
escapeHTML(server.address) +
(isIPv6 ? ']' : '');
var addrStr = server.address;
if (addrStr.indexOf(':') != -1)
addrStr = '[' + addrStr + ']';
var shortStr = addrStr;
addrStr += ':' + server.port;
var str = '<span'
if (shortStr.length > 25) {
shortStr = shortStr.substr(0, 23) + "&hellip;";
str += ' class="mts_tooltip" title="' + addrStr + '"'
if (shortStr.length > 26) {
shortStr = shortStr.substring(0, 25) + "\u2026";
str += ' title="' + escapeHTML(addrStr) + '"'
}
if (server.port != 30000)
shortStr += ':' + server.port;
return str + '>' + shortStr + '</span>';
return str + '>' + escapeHTML(shortStr) + '</span>';
}
function tooltipString(str, maxLen) {
function tooltipString(str) {
str = escapeHTML(str);
var shortStr = str;
var ret = '<span';
if (shortStr.length > maxLen) {
shortStr = shortStr.substr(0, maxLen - 2) + "&hellip;";
ret += ' class="mts_tooltip" title="' + str + '"';
}
return ret + '>' + shortStr + '</span>';
return '<span title="' + str + '">' + str + '</div>';
}
function hoverList(name, list) {
if (!list || list.length == 0) return '';
if (!list || list.length == 0)
return '';
var str = '<div class="mts_hover_list">'
str += name + ' (' + list.length + ')<br />';
str += '<b>' + escapeHTML(name) + '</b> (' + list.length + ')<br />';
for (var i in list) {
str += escapeHTML(list[i]) + '<br />';
}
return str + '</div>';
}
function hoverString(name, string) {
if (!string) return '';
return '<div class="mts_hover_list">'
+ name + ':<br />'
+ escapeHTML(string) + '<br />'
function hoverString(name, str) {
if (!str)
return '';
if (typeof(str) != 'string')
str = str.toString();
return '<div class="mts_hover_list">'
+ '<b>' + escapeHTML(name) + '</b>:<br />'
+ escapeHTML(str) + '<br />'
+ '</div>';
}
function draw(json) {
var html = window.render.servers(json);
jQuery(master.output).html(html);
function constantWidth(str, width) {
if (typeof(str) != 'string')
str = str.toString();
return '<span class="mts_cwidth" style="width:' + width + 'em;">' + escapeHTML(str) + '</span>';
}
function get() {
jQuery.getJSON(master.list_url, draw);
}
// Code that fetches & displays the actual list
function loaded(){
if (!master.no_refresh) {
setInterval(get, 60 * 1000);
master.draw = function(json) {
if (json == null)
return;
// pre-filter by chosen protocol range
var tmp = master.proto_range ? JSON.parse(master.proto_range) : null;
if (tmp) {
json = {
list: json.list.filter(function(server) {
return !(tmp[0] > server.proto_max || tmp[1] < server.proto_min);
}),
total: {clients: 0},
total_max: {clients: "?", servers: "?"}
};
json.list.forEach(function(server) { json.total.clients += server.clients; });
json.total.servers = json.list.length;
}
get();
}
var html = window.render.servers(json);
jQuery('#server_list').html(html);
jQuery('.proto_select', '#server_list').on('change', function(e) {
master.proto_range = e.target.value;
master.draw(master.cached_json); // re-render
});
};
master.get = function() {
jQuery.getJSON(master.list_url, function(json) {
master.cached_json = json;
master.draw(json);
});
};
master.loaded = function() {
if (!master.no_refresh)
setInterval(master.get, 60 * 1000);
master.get();
};
master.showAll = function() {
delete master.min_clients;
delete master.limit;
master.get();
};
// https://github.com/pyrsmk/toast
this.toast=function(){var e=document,t=e.getElementsByTagName("head")[0],n=this.setTimeout,r="createElement",i="appendChild",s="addEventListener",o="onreadystatechange",u="styleSheet",a=10,f=0,l=function(){--f},c,h=function(e,r,i,s){if(!t)n(function(){h(e)},a);else if(e.length){c=-1;while(i=e[++c]){if((s=typeof i)=="function"){r=function(){return i(),!0};break}if(s=="string")p(i);else if(i.pop){p(i[0]),r=i[1];break}}d(r,Array.prototype.slice.call(e,c+1))}},p=function(n,s){++f,/\.css$/.test(n)?(s=e[r]("link"),s.rel=u,s.href=n,t[i](s),v(s)):(s=e[r]("script"),s.src=n,t[i](s),s[o]===null?s[o]=m:s.onload=l)},d=function(e,t){if(!f)if(!e||e()){h(t);return}n(function(){d(e,t)},a)},v=function(e){if(e.sheet||e[u]){l();return}n(function(){v(e)},a)},m=function(){/ded|co/.test(this.readyState)&&l()};h(arguments)};
toast(master.root + 'style.css', master.root + 'servers.js', function() {
if (typeof(jQuery) != 'undefined')
return loaded();
return master.loaded();
else
toast('//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js', loaded);
toast('//ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js', master.loaded);
});

9
static/modern-normalize.min.css vendored Normal file
View File

@@ -0,0 +1,9 @@
/**
* Minified by jsDelivr using clean-css v5.3.2.
* Original file: /npm/modern-normalize@3.0.1/modern-normalize.css
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
/*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */
*,::after,::before{box-sizing:border-box}html{font-family:system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji';line-height:1.15;-webkit-text-size-adjust:100%;tab-size:4}body{margin:0}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentcolor}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}
/*# sourceMappingURL=/sm/d2d8cd206fb9f42f071e97460f3ad9c875edb5e7a4b10f900a83cdf8401c53a9.map */

View File

@@ -1,18 +1,26 @@
{{? !master.no_total}}
<div class="total">
Players: {{=it.total.clients}}/{{=it.total_max.clients}}&nbsp;
Servers: {{=it.total.servers}}/{{=it.total_max.servers}}
<div>
<span class="header_total">
Players: {{=it.total.clients}}/{{=it.total_max.clients}}&nbsp;
Servers: {{=it.total.servers}}/{{=it.total_max.servers}}
</span>
{{? master.show_proto_select}}
, Protocol: <select class="proto_select">
<option value="">All</option>
<option value="[11,32]" {{? master.proto_range=='[11,32]'}}selected{{?}}>11-32 (0.4 series)</option>
<option value="[37,99]" {{? master.proto_range=='[37,99]'}}selected{{?}}>37+ (5.0 or newer)</option>
</select>{{?}}
</div>
{{?}}
<table>
<thead><tr>
{{? !master.no_address}}<th>Address[:Port]</th>{{?}}
{{? !master.no_clients}}<th>Players / Max{{? !master.no_avgtop}}<br/>Average / Top{{?}}</th>{{?}}
{{? !master.no_version}}<th>Version, Subgame, Mapgenerator</th>{{?}}
{{? !master.no_version}}<th class="version">Version, Game, Mapgen</th>{{?}}
{{? !master.no_name}}<th>Name</th>{{?}}
{{? !master.no_description}}<th>Description</th>{{?}}
{{? !master.no_flags}}<th>Flags</th>{{?}}
{{? !master.no_uptime}}<th>Uptime, Age</th>{{?}}
{{? !master.no_flags}}<th class="flags">Flags</th>{{?}}
{{? !master.no_uptime}}<th class="uptime">Uptime, Age</th>{{?}}
{{? !master.no_ping}}<th>Ping, Lag</th>{{?}}
</tr></thead>
<tbody>
@@ -21,56 +29,53 @@
{{ if (master.min_clients && server.clients < master.min_clients) continue;}}
<tr>
{{? !master.no_address}}
<td class ="address">
<td class="address">
{{=addressString(server)}}
</td>{{?}}
{{? !master.no_clients}}
<td class="clients{{? server.clients_list && server.clients_list.length > 0}} mts_hover_list_text{{?}}">
{{=server.clients}}/{{=server.clients_max}}{{? !master.no_avgtop}} &nbsp;&nbsp;{{=Math.floor(server.pop_v)}}/{{=server.clients_top}}{{?}}
{{=constantWidth(server.clients + '/' + server.clients_max, 3.4)}}
{{? !master.no_avgtop}} {{=constantWidth(Math.floor(server.pop_v) + '/' + server.clients_top, 3.4)}}{{?}}
{{=hoverList("Clients", server.clients_list)}}
</td>{{?}}
{{? !master.no_version}}
<td class="version{{? server.mods && server.mods.length > 0}} mts_hover_list_text{{?}}">
{{=escapeHTML(server.version)}}, {{=escapeHTML(server.gameid)}},&nbsp;
{{=escapeHTML(server.mapgen || '?')}}
{{!server.version}}, {{!server.gameid}}
{{? server.mapgen}}, {{!server.mapgen}}{{?}}
{{=hoverList("Mods", server.mods)}}
</td>{{?}}
{{? !master.no_name}}
<td class="name">
{{? server.url}}
<a href="{{=escapeHTML(server.url)}}">{{=tooltipString(server.name, 25)}}</a>
<a href="{{!server.url}}" target="_blank">{{=tooltipString(server.name)}}</a>
{{??}}
{{=tooltipString(server.name, 25)}}
{{=tooltipString(server.name)}}
{{?}}
</td>{{?}}
{{? !master.no_description}}
<td class="description">
{{=tooltipString(server.description, 50)}}
{{=tooltipString(server.description)}}
</td>{{?}}
{{? !master.no_flags}}
<td class="flags {{? server.privs}} mts_hover_list_text{{?}}">
{{=hoverString("Privs", server.privs)}}
{{=hoverString("Default privileges", server.privs)}}
{{=server.creative ? 'Cre ' : ''}}
{{=server.dedicated ? 'Ded ' : ''}}
{{=server.damage ? 'Dmg ' : ''}}
{{=server.liquid_finite ? 'Liq ' : ''}}
{{=server.pvp ? 'PvP ' : ''}}
{{=server.password ? 'Pwd ' : ''}}
{{=server.rollback ? 'Rol ' : ''}}
{{=server.can_see_far_names ? 'Far ' : ''}}
</td>{{?}}
{{? !master.no_uptime}}
<td class="uptime">
{{=humanTime(server.uptime)}}, {{=humanTime(server.game_time)}}
{{=constantWidth(humanTime(server.uptime), 3.2)}} / {{=constantWidth(humanTime(server.game_time), 3.2)}}
</td>{{?}}
{{? !master.no_ping}}
<td class="ping">
{{=Math.floor(server.ping * 1000)}}{{? server.lag}}, {{= Math.floor(server.lag * 1000)}}{{?}}
{{=constantWidth(Math.floor(server.ping * 1000), 1.8)}}{{? server.lag}} / {{=constantWidth(Math.floor(server.lag * 1000), 1.8)}}{{?}}
</td>{{?}}
</tr>
{{~}}
</tbody>
</table>
{{? master.min_clients || master.limit}}
<a class="clickable" onclick="delete master.min_clients; delete master.limit; get();">More...</a>
<a href="javascript:master.showAll()">Show all...</a>
{{?}}

View File

@@ -1,4 +1,4 @@
#server_list .total {
#server_list .header_total {
font-weight: bold;
}
@@ -12,42 +12,66 @@
}
#server_list td, #server_list th {
border: 1px solid gray;
border: 1px solid #2A3132;
padding: 5px;
color: inherit;
}
#server_list thead {
background-color: #FFA;
background-color: #2A3132;
border-bottom: 5px solid #336B87;
color: white;
font-size: 1.1em;
}
#server_list tbody tr:nth-child(even) {
background-color: #EEE;
background-color: #E0E0E0;
}
#server_list tbody tr:hover {
background-color: #CCC;
#server_list td.clients, #server_list td.uptime, #server_list td.ping {
text-align: center;
}
#server_list td.version, #server_list td.name, #server_list td.description {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
/* Note: the column widths here are not exact as auto-layout is left enabled */
#server_list td.address {
max-width: 24ch;
}
#server_list td.version {
max-width: 16ch;
}
#server_list td.name {
max-width: 32ch;
}
#server_list td.description {
max-width: 70ch;
}
.mts_hover_list {
display: none;
border: 1px solid #88F;
border-radius: 4px;
background-color: white;
border: 1px solid #336B87;
border-radius: 10px;
background-color: #FFF;
position: absolute;
z-index: 100;
padding: 0.5em;
box-shadow: 1px 1px 5px 3px rgba(0, 0, 0, 0.25);
}
.mts_hover_list b {
font-size: 1.1em;
}
td:hover .mts_hover_list {
display: block;
}
.mts_hover_list_text, .mts_tooltip {
text-decoration: underline;
text-decoration-style: dashed;
.mts_cwidth {
display: inline-block;
}
.clickable {
text-decoration: underline;
cursor: pointer;
}