Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fda88af676 | ||
|
|
2f66e1deca | ||
|
|
258add93e0 | ||
|
|
de30a4e1ac | ||
|
|
b93b50ad11 | ||
|
|
431ac110c7 | ||
|
|
aedabc50a8 | ||
|
|
6df3b93f48 | ||
|
|
f71be0af67 | ||
|
|
cd0f2a56d0 | ||
|
|
4d320bbcf5 | ||
|
|
5d191896f3 | ||
|
|
77951100b9 | ||
|
|
6edaa91315 | ||
|
|
a8a9d92077 | ||
|
|
3d08cd4ff4 | ||
|
|
dcc8d5ec74 | ||
|
|
df032cb47c | ||
|
|
967c1a0b51 | ||
|
|
4533842e41 | ||
|
|
1f1af8828c | ||
|
|
4584459fca | ||
|
|
85aff93b02 | ||
|
|
17b52cd647 | ||
|
|
78e6c48c85 | ||
|
|
cb8fa58df4 | ||
|
|
c02ed9f07a | ||
|
|
d945b26f9f | ||
|
|
12ed8aff60 | ||
|
|
6fba704bb0 | ||
|
|
218f8d9bc5 | ||
|
|
b0fed2e9f7 | ||
|
|
d2f09370f7 | ||
|
|
84a40e406b | ||
|
|
2f4ffde916 | ||
|
|
5d5f31d295 | ||
|
|
a9ecf55b38 | ||
|
|
9f144f3e3c | ||
|
|
e37149a834 | ||
|
|
a5bc675a6e | ||
|
|
578a7bc987 | ||
|
|
e99ecd6582 | ||
|
|
ddcd98a457 | ||
|
|
56ece3ba3d | ||
|
|
ba0077a4f5 | ||
|
|
04810a094c | ||
|
|
85c3048cd4 | ||
|
|
772fc29cb8 | ||
|
|
ac66259801 | ||
|
|
794807c9ff | ||
|
|
8aa2efd5eb | ||
|
|
8d0c99b5d0 | ||
|
|
6f51e2f00f | ||
|
|
e7c4d2c20a | ||
|
|
67d8515fd8 | ||
|
|
57fb13cbb8 | ||
|
|
0a3d05baf5 | ||
|
|
a2b47ff52b | ||
|
|
e49da8f1b9 | ||
|
|
48020105af | ||
|
|
f43f201af5 | ||
|
|
23d45c0a15 | ||
|
|
f5bddaaef5 | ||
|
|
78abbee771 | ||
|
|
2f87286475 | ||
|
|
0d93321f6d | ||
|
|
da9f297346 | ||
|
|
afd7b16e5b | ||
|
|
828a1fda7e | ||
|
|
705ea6e1a0 | ||
|
|
5de6082f57 | ||
|
|
5e12cb5022 | ||
|
|
58f03d0395 |
8
.editorconfig
Normal file
8
.editorconfig
Normal 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
28
.github/workflows/lint.yml
vendored
Normal 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
9
.gitignore
vendored
@@ -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
168
README.md
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
APScheduler>=3
|
||||
Flask>=0.10
|
||||
Flask-PyMongo>=0.3
|
||||
|
||||
Flask>=2.0.0
|
||||
maxminddb>=2.0.0
|
||||
|
||||
@@ -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>
|
||||
|
||||
137
static/list.js
137
static/list.js
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
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) + "…";
|
||||
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) + "…";
|
||||
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
9
static/modern-normalize.min.css
vendored
Normal 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 */
|
||||
@@ -1,18 +1,26 @@
|
||||
{{? !master.no_total}}
|
||||
<div class="total">
|
||||
Players: {{=it.total.clients}}/{{=it.total_max.clients}}
|
||||
Servers: {{=it.total.servers}}/{{=it.total_max.servers}}
|
||||
<div>
|
||||
<span class="header_total">
|
||||
Players: {{=it.total.clients}}/{{=it.total_max.clients}}
|
||||
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}} {{=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)}},
|
||||
{{=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>
|
||||
{{?}}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user