Die Panel-Anbindung entfaellt -- das Panel postet jetzt selbst
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s

Entscheidung des Betreibers am 19.08.2026: das d4rk_gameserver-Panel
baut und stellt seine Status-Embeds ab sofort selbst in den Discord.
Damit hat diese Anbindung keinen Zweck mehr.

WARUM SIE UEBERHAUPT WEG SOLL, und nicht bloss ungenutzt liegenbleibt:
sie war die zweite Meinung ueber denselben Zustand. `zustandText()` gab
`null` zurueck, wenn ein Server LIEF UND BEREIT war -- also im
Normalfall -- und der Monitor fiel dann auf seine gamedig-Abfrage
zurueck. Erreichte die den Server nicht, stand im Kanal "Offline",
waehrend dasselbe Embed RAM und CPU aus dem Panel anzeigte. Ein
Widerspruch, den man von keiner der beiden Seiten aufloesen konnte.

ENTFERNT:

    src/panel.js                      die Anbindung
    src/panel-abgleich.js             das Anlegen/Wegraeumen von Eintraegen
    tools/panel-pruefen.mjs           die zugehoerigen Proben
    tools/panel-abgleich-pruefen.mjs
    panelUrl/panelZeichen             in runtime-settings.js
    panel_url/panel_zeichen           als Einstellung und im Formular
    Servertyp `panel`                 er fragte nicht selbst, sondern las ab

WAS DER MONITOR DAMIT VERLIERT, und es gehoert dazugesagt: die
Schonfrist beim Hochfahren. Ein startendes Modpack antwortet minutenlang
nicht -- am ATM10 gemessen 306 Sekunden -- und meldet dadurch wieder
einen Ausfall, wo vorher "startet gerade" stand. Die
Zwei-Fehlschlaege-Schwelle federt das teilweise ab, nicht ganz.

Fuer Server im Panel ist das kein Verlust: dort postet das Panel und
kennt den Unterschied. Fuer FiveM und LS25 hat es ihn nie gegeben.

Geprueft: alle 281 relativen Importe loesen auf, die geaenderten Dateien
sind syntaktisch in Ordnung, das Frontend baut. Der Bot selbst ist NICHT
neu gestartet worden -- das ist eine Entscheidung des Betreibers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-19 12:01:21 +02:00
co-authored by Claude Opus 5
parent 0d7628f14e
commit 19010e9fde
9 changed files with 37 additions and 775 deletions
+26 -114
View File
@@ -8,8 +8,6 @@ import { brandFooter, botStatusText } from '../runtime-settings.js';
import { brandEmbed } from '../embeds.js';
import { moduleEnabled } from '../modules.js';
import { tuning, tuningMs, everyTuned } from '../tuning.js';
import { panelStatus, panelEintrag, zustandText, lastText, startetNoch, gewolltAus } from '../panel.js';
import { abgleichen, embedWegraeumen } from '../panel-abgleich.js';
const GREEN = 0x23a55a;
const RED = 0xf23f43;
@@ -18,7 +16,7 @@ const RED = 0xf23f43;
// standen mal ausgedachte wie "arkse" oder "terraria", die es dort gar nicht
// gibt. Beim Ändern gegen `games` aus gamedig prüfen, nicht gegen das Gefühl.
export const GAME_ICONS = {
fivem: '🚗', http: '🌐', minecraft: '⛏️', mbe: '⛏️', panel: '🛰️',
fivem: '🚗', http: '🌐', minecraft: '⛏️', mbe: '⛏️',
teamfortress2: '🎩', counterstrike2: '🔫', csgo: '🔫', garrysmod: '🔧',
rust: '🪓', valheim: '🛡️', palworld: '🐑', ase: '🦖', asa: '🦖', pixark: '🦖',
dayz: '🧟', projectzomboid: '🧟', sdtd: '🧟',
@@ -27,13 +25,14 @@ export const GAME_ICONS = {
};
// fivem und http sind unsere eigenen Typen — gamedig kennt sie nicht.
// `panel` ist der dritte, und er ist anders als die beiden: da fragt der Bot
// gar nicht, sondern das Panel hat schon gefragt und schickt die Zahl mit.
// Siehe `queryServer`.
//
// `panel` GAB ES HIER ALS DRITTEN, und er ist am 19.08.2026 entfallen: das
// d4rk_gameserver-Panel baut und postet seine Status-Embeds seither selbst.
// Ein Typ, der auf eine Anbindung zeigt, die es nicht mehr gibt, wäre ein
// Eintrag, den man anlegen kann und der dann dauerhaft „offline" meldet.
const EIGENE = {
fivem: { name: 'FiveM', port: 30120 },
http: { name: 'HTTP-Check', port: null },
panel: { name: 'Über das Panel', port: null },
};
// Protokolle, die ohne Zugangs-Code gar nicht antworten. Der Code steht im
@@ -78,14 +77,9 @@ const alertState = new Map(); // id → { fails, down, since }
export const lastResults = new Map(); // id → Query-Ergebnis + checkedAt
/**
* Einen Server abfragen — fivem/http direkt, `panel` gar nicht, alles andere
* über gamedig.
*
* `panelItems` ist der Statusbericht des Panels aus DIESEM Durchlauf. Er ist
* optional: fehlt er, verhält sich alles wie vorher, und ein `panel`-Eintrag
* gilt als offline — mit Grund.
* Einen Server abfragen — fivem/http direkt, alles andere über gamedig.
*/
export async function queryServer(server, panelItems = null) {
export async function queryServer(server) {
const base = {
...server, online: false, players: null, max: null, map: null,
playerNames: [], ping: null, version: null, mods: null,
@@ -95,34 +89,6 @@ export async function queryServer(server, panelItems = null) {
};
const started = Date.now();
try {
// ================== DAS PANEL HAT SCHON GEFRAGT ====================
//
// Kein Netzverkehr von hier. Für TeamSpeak wäre er sogar unmöglich:
// gamedig braucht dafür den ServerQuery-Port, und der liegt beim
// Betreiber absichtlich auf `127.0.0.1` — der Bot im Container kommt
// dort nie hin. Das Panel läuft auf dem Host, darf hin und hat die
// Zugangsdaten. Also fragt es, und wir lesen ab.
//
// `spieler === null` HEISST „KEINE ANTWORT", NICHT NULL SPIELER. Das
// Panel garantiert dazu einen `spieler_grund` — entweder eine Zahl
// oder ein Grund, nie beides leer. Genau diese Zusage macht das hier
// sauber: wir werfen mit dem Grund und landen im selben Zweig wie ein
// gescheiterter gamedig-Query, statt eine 0 zu erfinden.
if (server.type === 'panel') {
const p = panelEintrag(panelItems, server);
if (!p) throw new Error('Das Panel kennt diesen Server nicht (mehr).');
if (p.spieler == null) throw new Error(p.spieler_grund || 'Das Panel nennt keine Zahl.');
return {
...base, online: true,
players: p.spieler,
max: p.spieler_max ?? null,
spielname: p.spiel ?? null,
// KEIN PING. Die Zahl ist eine gespeicherte Messung des Panels
// (bis zu zehn Minuten alt) und keine Antwortzeit von hier.
// Eine Millisekundenzahl hinzuschreiben wäre erfunden.
ping: null,
};
}
if (server.type === 'fivem') {
const res = await fetch(`${server.query_url.replace(/\/$/, '')}/dynamic.json`, {
signal: AbortSignal.timeout(tuningMs.seconds('monitor_timeout')),
@@ -298,14 +264,8 @@ export function buildServerEmbed(r) {
// nicht verlässlich, und dann stünde das Steuerzeichen wörtlich da.
kopf.push(`*${r.spielname.trim().slice(0, 200)}*`);
}
// Wenn GameDig nichts erreicht hat, das Panel aber sagt, dass der
// Container laeuft und noch startet: DAS ist die richtige Auskunft. Ein
// Modpack braucht Minuten bis zum ersten Spieler -- am ATM10 des
// Betreibers 306 Sekunden -- und ein rotes "Offline" waere in dieser Zeit
// schlicht falsch.
const ausPanel = r.online ? null : zustandText(r.panel);
kopf.push([
r.online ? '🟢 **Online**' : (ausPanel ?? '🔴 **Offline**'),
r.online ? '🟢 **Online**' : '🔴 **Offline**',
zugang.grund ? `🔒 ${zugang.grund}` : null,
r.online && r.ping != null ? `${r.ping} ms` : null,
].filter(Boolean).join(' · '));
@@ -323,12 +283,6 @@ export function buildServerEmbed(r) {
r.mods != null ? { name: 'Mods', value: String(r.mods), inline: true } : null,
]));
// Was nur das Panel weiss: was der Server gerade verbraucht.
{
const last = lastText(r.panel);
if (last) embed.addFields({ name: 'Auf dem Wirt', value: last });
}
if (r.online && r.playerNames.length > 0) {
embed.addFields({
name: `Wer drauf ist (${r.playerNames.length})`,
@@ -371,30 +325,19 @@ async function handleAlerts(client, r) {
alertState.set(r.id, { fails: 0, down: false, since: null });
return;
}
// KEIN ALARM, WENN DAS PANEL DEN ZUSTAND ERKLÄREN KANN.
// HIER STAND EINE SCHONFRIST AUS DEM PANEL: solange es meldete, der
// Container starte noch oder sei absichtlich gestoppt, kam kein Alarm.
// Mit der Panel-Anbindung ist sie entfallen (19.08.2026).
//
// Das ist der eigentliche Gewinn der Anbindung. GameDig *fragt* einen
// Server; ein startender antwortet nicht, und nach zwei Fehlversuchen
// stünde hier eine Ausfallmeldung. Bei einem Modpack sind das jedes Mal
// Minuten Fehlalarm — am ATM10 des Betreibers 306 Sekunden bis zum ersten
// Spieler. Das Panel *sieht* den Container und weiß, dass er läuft und
// nur das Fertig-Merkmal noch nicht durch ist.
// WAS DAS KOSTET, und es gehört dazugesagt: ein Modpack, das startet,
// antwortet minutenlang nicht — am ATM10 gemessen 306 Sekunden. Für
// solche Server meldet der Monitor jetzt wieder einen Ausfall, wo vorher
// „startet gerade" stand. Die Zwei-Fehlschläge-Schwelle unten federt das
// teilweise ab, aber nicht ganz.
//
// Zwei Fälle, eine Behandlung: nichts tun. Der Zähler bleibt dabei
// ausdrücklich STEHEN statt zurückgesetzt zu werden, aus zwei Gründen:
//
// - Läuft die Gnadenfrist ab, ohne dass der Server fertig wird, greift
// `startetNoch` nicht mehr und der Alarm kommt sofort statt erst nach
// zwei weiteren Durchläufen.
// - Ein bereits gemeldeter Ausfall bleibt gemeldet. Sonst verschluckt
// ein Stopp im Panel die spätere „✅ wieder online"-Entwarnung, und im
// Alarm-Kanal bliebe ein 🚨 ohne Auflösung stehen.
//
// Und was NICHT unterdrückt wird: ein Absturz, ein OOM-Kill und ein
// fehlender Container. Das sind die Fälle, für die es den Kanal gibt —
// hier hilft das Panel dem Alarm, statt ihn zu ersetzen.
if (startetNoch(r.panel) || gewolltAus(r.panel)) return;
// Für Server, die im d4rk_gameserver-Panel stehen, ist das kein Verlust:
// das Panel postet deren Status selbst und kennt den Unterschied. Für
// Server, die nur hier stehen (FiveM, LS25), hat es ihn nie gegeben.
const fails = s.fails + 1;
if (!s.down && fails >= tuning('monitor_fails')) {
@@ -417,46 +360,15 @@ async function handleAlerts(client, r) {
export async function monitorTick(client) {
if (!moduleEnabled('server_monitor')) return;
// DER PANEL-BERICHT WIRD VOR ALLEM ANDEREN GEHOLT, und der Abgleich läuft
// VOR der Prüfung auf einen leeren Monitor.
//
// Vorher stand hier zuerst `if (servers.length === 0) return`. Damit hätte
// der Schalter im Panel beim ALLERERSTEN Server nichts getan -- der
// Durchlauf wäre ausgestiegen, bevor er das Panel überhaupt fragt. Und
// zwar stillschweigend, was der schlechteste aller Fehlermodi ist: der
// Schalter steht auf an, im Kanal passiert nichts, und es gibt nirgends
// eine Meldung, an der man sich entlanghangeln könnte.
//
// Der Aufruf ist billig: `panelStatus` hält seine Antwort 20 Sekunden und
// gibt ohne eingetragene Adresse sofort null zurück.
const panelItems = await panelStatus();
if (panelItems) {
const { angelegt, uebernommen, entfernt } = abgleichen(panelItems);
if (entfernt.length > 0) await embedWegraeumen(client, entfernt);
if (angelegt.length + uebernommen.length + entfernt.length > 0) {
console.log(
`[panel] neu: ${angelegt.join(', ') || '—'}`
+ ` | uebernommen: ${uebernommen.join(', ') || '—'}`
+ ` | entfernt: ${entfernt.map((e) => e.name).join(', ') || '—'}`,
);
}
}
const servers = listGameservers();
if (servers.length === 0) return;
// NICHT `servers.map(queryServer)` -- so stand es hier, und mit dem
// zweiten Parameter wäre es eine Falle gewesen: `map` reicht (element,
// index, array) weiter, `panelItems` bekäme also die Zahl 0. Der erste
// Server hätte damit `null` gesehen und alle weiteren eine Zahl, an der
// `panelEintrag` still scheitert -- ein Fehler, der nur beim zweiten
// Eintrag auftritt und nach Zufall aussieht.
const results = await Promise.all(servers.map((s) => queryServer(s, panelItems)));
// Die Zusatzauskunft vom Panel: EINMAL je Durchlauf, nicht je Server.
// Sie ist optional -- ist nichts eingetragen oder antwortet das Panel
// nicht, bleibt `panel` schlicht null und alles läuft wie vorher.
for (const r of results) r.panel = panelEintrag(panelItems, r);
// NICHT `servers.map(queryServer)`, sondern der Pfeil drumherum: `map`
// reicht (element, index, array) weiter, und ein zweiter Parameter bekäme
// damit stillschweigend die Zahl 0. Der Aufruf hat heute nur einen —
// trotzdem bleibt der Pfeil stehen, damit ein zweiter niemandem zur Falle
// wird.
const results = await Promise.all(servers.map((s) => queryServer(s)));
// Verlauf für die Web-Seite festhalten + Cache fürs API
for (const r of results) {