Repository navigation
Connection Journal
The Connection Journal (sidebar, Security) lists who signed in to this web interface and who joined a TeamSpeak server, from which address, and whether it worked. It is for admins only and records on its own - the web side needs nothing switched on, the TeamSpeak side works for every connection unless you turn it off for it. It is also the place to see who is trying passwords against your installation.
Every address also gets a country, looked up offline, and any address can be banned from the list with one click.
Every one of these is one row, successful or not:
| Event | What it is | Reasons you may see |
|---|---|---|
| Sign-in | the password step | no such account, account disabled, a single-sign-on account (it has no password), wrong password; for a success "password correct, second factor still needed" or "trusted device, second factor skipped" |
| Second factor | the code step of two-factor login | wrong code, expired or invalid step; for a success, "signed in with a recovery code" |
| Single sign-on | a sign-in through your identity provider | not configured, invalid or expired state, no account id sent, the provider's answer refused, account disabled |
| Rate limit | a request the sign-in limit turned away | too many sign-in attempts from this address (or too many token refreshes) |
Each row carries the time, the address, the account name, the browser (shown as, say, "Firefox 130 · Windows") and the reason. The rate-limit event is written once per address and minute, however many requests the limit keeps refusing.
Not recorded: the token refresh a signed-in browser does every quarter of an hour, and anything inside the app after the sign-in.
About the account name. For an account that exists it is that account. For a name nobody has, it is what was typed - marked "unknown" in the list. People sometimes paste their password into the name field, so such a row can contain one. Treat the journal like the sensitive data it is, and keep it only as long as you need it (see below).
One row per client connection: the nickname, the unique ID, the address, the client's version and platform, which connection and virtual server, when it joined and - when it has left - when, how long it stayed and the reason TeamSpeak gave (leaving, connection lost, ...). A client that is still there shows as online now.
How the joins are seen depends on the connection, and Settings → Connections has a Record who joins switch for each of them:
- With an SSH login the app listens to the server's live events. Nothing is missed, however short the visit, and every end comes with its reason. TeamSpeak's event carries no address, so each join is followed by one question to the server for it.
- Without SSH (or while it is down) the app asks the server who is there every 15 seconds. A client that joins and leaves between two questions is not seen, and the end of a session has no reason - the page shows "the end was not seen".
- With neither an API key nor an SSH login there is nothing to ask the server with; the Settings tab of the page says so.
What cannot appear: a connection the server refuses - a banned client, flood protection - leaves neither an event nor a log line on the server, so the journal never hears of it.
Not recorded by default, each with a switch on the page's Settings tab:
- ServerQuery clients - other tools, and this app's own connections. Every request the app makes to a server is one, so with this on the journal gets very busy.
- This app's own music bots, recognised by their unique ID - a bot that reconnects would add a row every time.
The journal's own SSH sessions are never recorded.
When the app restarts, clients that stayed keep their row; the ones that left meanwhile are closed as "the end was not seen". If recording is switched off for a connection (or the whole journal) while clients are on it, their rows are closed as "still there when recording stopped".
The page opens on the last seven days. Above the list:
- a search for part of an account name, nickname, unique ID, connection name or address,
- the period (24 hours, 7 days, 30 days, everything),
- the source (web or TeamSpeak), the event and the result,
- the country, once a country database is installed - the list offers the countries that occur, with how many entries each has,
- for TeamSpeak, the server connection (when there is more than one) and Online now, which keeps just the clients on a server right now,
- an address chip - click any address in the list and the list narrows to exactly that address; the chip's cross or Reset filters takes it away.
Click a column heading to sort by it (time, source, event, result, account, address, country, details); a second click reverses it. The list is sorted and paged by the server, 25, 50, 100 or 200 entries at a time, so it stays fast on a large journal, and it refreshes itself every 15 seconds. Addresses sort as text, not by number.
The By address tab shows the same entries as one line per address: how many entries it has, how many failed and succeeded, how many different accounts were tried from it, and when it was last seen. It uses the same filters. An address with dozens of failures and a dozen different account names is a guess-the-password attempt; the same address with one account and a few failures is somebody mistyping.
On the web side the address is the one the backend works out from the proxy in front of it - if that is not set up right, the journal shows your proxy instead of the visitor. Settings → Network checks it, and Deployment explains TRUST_PROXY.
On the TeamSpeak side it is the address TeamSpeak reports for the client, and only if the account the app uses may see it (the permission b_client_remoteaddress_view; the default admin has it). When it may not, the address column says "unknown" and the page's Settings tab names the permission. Behind a NAT or a Docker network, TeamSpeak sees what the connection came from: with the server in a container, that can be the Docker gateway.
Every address gets a country - with the City database also a region and a city - looked up in a database file on your server. No address is sent anywhere for it. Until a database is installed the country column stays empty. The file lives in the backend's data directory, so it survives a container rebuild like everything else there.
Getting a database - the Country lookup card on the page's Settings tab:
-
Download fetches DB-IP's free Lite database from
download.db-ip.com: the Country edition (a download of about 4 MB) by default, or the City edition (about 60 MB, and about 120 MB of memory once loaded) if you pick it in the card. That download is the only request the feature makes to the outside, and only when you click the button or the monthly update is on. It fetches the current month's release; early in a month, before DB-IP has published it, last month's. -
Upload takes a database of your own, as
.mmdbor.mmdb.gz- a MaxMind GeoLite2 Country or City file, say. MaxMind's database needs your own account and licence, so the app neither bundles nor downloads it. Only a country or city database is accepted; anything else (an ASN database, a damaged file) is refused and what was installed stays. - Update by itself every month is off by default. When on, the app checks every six hours whether DB-IP has published a newer release of the selected edition and fetches it. A file you uploaded yourself is never replaced.
What you see. The country column shows the country, and under it the city and region when the City database is installed. An address in a private range shows "private network", one on the machine itself "this machine", and a dash means no country is known - the database has no entry for it, or no database is installed yet. The country filter and the sort work in both lists, and the search box also finds a city.
Older entries. Rows that were already in the journal when you installed a database are looked up afterwards; the card shows how far that is. A row keeps the country it was stored with - a newer release later does not rewrite it, and switching from Country to City adds no city to rows already stored.
A country worked out from an address is an estimate: mobile, VPN and company networks often show where the provider is, and a city is less reliable than a country. The page credits DB-IP at its foot ("IP Geolocation by DB-IP", licensed CC BY 4.0) when its data is in use; with a MaxMind file it carries MaxMind's own line instead.
The small ban button next to an address (in the Journal and in the By address tab) opens a dialog with two ways to turn the address away. Either one, or both:
-
Block it on this web interface. Every request from the address is answered with a 403 and the sentence "Your address is blocked" - the whole API, the sign-in, public routes and the images of a widget included, and the WebSocket. Only
/api/healthstays open, so a monitor behind the same address still sees the backend alive. Sockets the address has open are closed when the ban is set. - Ban it on a TeamSpeak server. A ban rule for exactly that address in the server's own ban list: on the virtual server the row comes from, or on all virtual servers of the connection. For a row from the web side you choose the connection and the virtual server yourself. This ban is TeamSpeak's: it shows on the Bans page, and it is lifted there.
Both take how long - an hour, a day, a week (preselected), 30 days or until lifted - and an optional reason. A web ban ends by itself when its time is up; banning an address that is banned already renews the ban.
What is banned. Always the single address, also for IPv6, never a range: somebody with a whole IPv6 network can come back from another address. Addresses written differently (::ffff:1.2.3.4 and 1.2.3.4, or a long and a short form of an IPv6 address) are the same address.
What cannot be banned, and what asks twice. To keep an admin from locking themselves out:
- the address the dialog is opened from, this machine and private networks cannot be banned - behind a proxy that is not set up right every visitor looks like a private address, and a TeamSpeak server in a container sees the Docker gateway as the address of every client,
- an address an admin signed in from in the last 7 days needs a second confirmation in the dialog,
- and if it happens anyway, the environment variable
IP_BANS_DISABLED=true(Configuration) switches the web bans off from outside the interface - set it, restart the backend, lift the ban, unset it (Troubleshooting). While it is on, nothing is turned away and a new web ban is refused; the list stays readable.
Seeing the bans. A red Blocked sits under an address the web interface turns away, and a Banned (TeamSpeak) under a TeamSpeak row whose client a ban on that virtual server covers - by address, unique ID or nickname, so a ban set on the Bans page shows too (the ban lists are read at most every 30 seconds). The Bans tab lists the web bans in force: the address and its country, the reason, who set it and when, when it ends, how many requests it has turned away and when the last one came, and Lift. Requests a ban turns away are only counted there; they do not fill the journal.
A ban only works as well as the address the backend sees: with the wrong TRUST_PROXY it hits your proxy or misses the visitor. A ban answers new requests - an event stream (the Query Console's live events) that was open already runs until its connection ends.
The Settings tab of the page:
- Record sign-ins and joins - the master switch. Off, nothing new is written, on the web or on TeamSpeak; what is stored stays until it is pruned or cleared.
- The two switches for ServerQuery clients and own music bots, described above.
- Keep for (days) - 30 by default, 1 to 365.
- Keep at most (entries) - 100 000 by default, 1 000 to 1 000 000. Beyond it the oldest entries go first, whatever their age.
- The journal is pruned every hour, once at start, and at once when you save the settings.
- TeamSpeak servers watched lists each virtual server that is being watched - by live events or every 15 seconds - with how many clients are on it, and says why one cannot be: no API key and no SSH login, SSH refused, the server not answering, or no permission to see addresses.
- Country lookup is described above.
- Clear the journal deletes everything after a confirmation; the page says how many entries are stored and how old the oldest is.
Addresses are personal data in many places, so how long to keep them is your decision. This page is not legal advice; Privacy and Hosting lists everything the app stores.
v0.4, last edited 09.10.2026
Getting started
Using the app
Running it
When something is odd
Contributing