Die Panel-Anbindung entfaellt -- das Panel postet jetzt selbst
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:
+26
-114
@@ -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) {
|
||||
|
||||
@@ -268,10 +268,6 @@ export const MODULES = [
|
||||
// Optional. GameDig FRAGT einen Server -- ein startender antwortet
|
||||
// nicht, und das sieht aus wie ein Ausfall. Das Panel SIEHT den
|
||||
// Container und kann "startet gerade" von "aus" unterscheiden.
|
||||
{ setting: 'panel_url', label: 'Panel-Adresse', art: 'text',
|
||||
hint: 'optional, z. B. https://panel.d4rkst3r.de — unterscheidet „startet gerade" von „offline"' },
|
||||
{ setting: 'panel_zeichen', label: 'Panel-Zeichen', art: 'text',
|
||||
hint: 'im Panel unter „Zugang für Programme" anlegen; darf nur lesen' },
|
||||
],
|
||||
tool: { tab: 'server', label: 'Game-Server verwalten' },
|
||||
},
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
// Server, die im Panel das Häkchen „Im Discord anzeigen" tragen, hier anlegen
|
||||
// — und wieder wegräumen, wenn es fällt.
|
||||
//
|
||||
// WARUM DIESE RICHTUNG. Der naheliegende Weg wäre: das Panel ruft die
|
||||
// Bot-API und legt selbst an. Dann müsste das Panel aber ein Zeichen halten,
|
||||
// das SCHREIBEN darf — und damit läge das mächtigere der beiden Geheimnisse
|
||||
// auf der Seite, die es nicht braucht. So bleibt es bei einem Zeichen, das nur
|
||||
// lesen kann, und der Bot holt den Statusbericht ohnehin alle 30 Sekunden ab.
|
||||
// Das Häkchen reist einfach mit.
|
||||
//
|
||||
// DER PREIS IST EINE HALBE MINUTE. Deshalb sagt das Panel beim Umlegen des
|
||||
// Schalters dazu, wie lange es dauert. Ein Knopf, der scheinbar nichts tut,
|
||||
// wird dreimal gedrückt.
|
||||
//
|
||||
// WAS HIER NICHT PASSIERT: kein Feld eines bestehenden Eintrags wird
|
||||
// überschrieben. Wer im Webinterface ein Bild, einen Verbinden-Link oder Regeln
|
||||
// ergänzt hat, soll das nicht beim nächsten Durchlauf verlieren. Angelegt,
|
||||
// übernommen und gelöscht wird — mehr nicht.
|
||||
//
|
||||
// „Übernommen" heißt: gab es den Server schon von Hand, bekommt er nur den
|
||||
// Herkunftsvermerk. Sonst täte der Schalter im Panel sichtbar nichts, und
|
||||
// genau das ist dem Betreiber passiert.
|
||||
|
||||
import { listGameservers, createGameserver, deleteGameserver, getSetting, db } from './db.js';
|
||||
|
||||
/** Einen bestehenden Eintrag dem Panel zuschreiben -- ohne ihn sonst anzufassen. */
|
||||
const uebernehmenStmt = db.prepare('UPDATE gameservers SET panel_name = ? WHERE id = ?');
|
||||
|
||||
/**
|
||||
* Taugt dieser Panel-Eintrag für einen Monitor-Eintrag?
|
||||
*
|
||||
* Drei Bedingungen, und alle drei sind im Panel schon geprüft — hier stehen
|
||||
* sie noch einmal, weil ein Bericht von einem älteren Panel kommen kann und
|
||||
* ein halber Eintrag schlimmer ist als keiner: er stünde dauerhaft auf
|
||||
* offline und schlüge Alarm.
|
||||
*
|
||||
* ================== WARUM NICHT MEHR `x.gamedig` ==========================
|
||||
*
|
||||
* Hier stand `x.gamedig`, also: „kennt unsere Bibliothek dieses Spiel?" Das
|
||||
* hat einen Fall ausgesperrt, den es wirklich gibt — TeamSpeak. Nicht weil
|
||||
* gamedig es nicht könnte (es hat `teamspeak3`), sondern weil der
|
||||
* ServerQuery-Port des Betreibers absichtlich auf `127.0.0.1` liegt: der Bot
|
||||
* läuft im Container und kommt dort nie hin. Am 16.08.2026 gegengeprüft,
|
||||
* beide Versuche vom Host aus gescheitert.
|
||||
*
|
||||
* Das Panel dagegen sitzt auf dem Host, darf dorthin und hat die Zugangsdaten
|
||||
* — es schickt seine eigene Messung seither im Statusbericht mit. Gefragt
|
||||
* wird deshalb `zeigbar`: „kann ÜBERHAUPT JEMAND diesen Server abfragen —
|
||||
* wir über gamedig oder das Panel für uns?"
|
||||
*
|
||||
* `?? Boolean(x.gamedig)` ist der Rückfall für ein ÄLTERES Panel, das
|
||||
* `zeigbar` noch nicht kennt. Ohne ihn verschwänden nach einem Bot-Update
|
||||
* schlagartig alle Einträge, und zwar lautlos.
|
||||
*/
|
||||
export function taugt(x) {
|
||||
const zeigbar = x?.zeigbar ?? Boolean(x?.gamedig);
|
||||
return Boolean(x?.discord && zeigbar && x.adresse && x.port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Einmal abgleichen.
|
||||
*
|
||||
* @returns {{angelegt: string[], uebernommen: string[],
|
||||
* entfernt: Array<{id: number, name: string, message_id: string|null}>}}
|
||||
* Die Entfernten kommen mit ihrer Nachrichten-Kennung zurück, damit
|
||||
* der Aufrufer das Embed löschen kann — hier gibt es keinen Client.
|
||||
*/
|
||||
export function abgleichen(items) {
|
||||
const ergebnis = { angelegt: [], uebernommen: [], entfernt: [] };
|
||||
if (!Array.isArray(items)) return ergebnis;
|
||||
|
||||
const vorhanden = listGameservers();
|
||||
const ausPanel = new Map(vorhanden.filter((s) => s.panel_name).map((s) => [s.panel_name, s]));
|
||||
const gewollt = new Set(items.filter(taugt).map((x) => x.name));
|
||||
|
||||
for (const x of items) {
|
||||
if (!taugt(x) || ausPanel.has(x.name)) continue;
|
||||
// Ein Server auf derselben Adresse und demselben Port ist schon da —
|
||||
// dann NICHT noch einmal anlegen. Sonst stünde derselbe Server zweimal
|
||||
// im Kanal, und niemand wüsste, welches Embed das richtige ist.
|
||||
//
|
||||
// ABER AUCH NICHT EINFACH ÜBERGEHEN. Vorher stand hier `continue`, und
|
||||
// damit tat der Schalter im Panel sichtbar nichts, sobald der Server
|
||||
// schon von Hand eingetragen war — keine Meldung, kein Embed, keine
|
||||
// Spur. Genau so ist es dem Betreiber gegangen, und er hat den Fehler
|
||||
// erst bei sich gesucht.
|
||||
//
|
||||
// Wer den Schalter umlegt, sagt „dieser Server gehört ans Panel". Also
|
||||
// wird der bestehende Eintrag übernommen statt ignoriert.
|
||||
//
|
||||
// Der Preis gehört benannt: ab dann nimmt ein Ausschalten den Eintrag
|
||||
// mit, samt Bild und Links, die von Hand daran hängen. Das ist der Sinn
|
||||
// des Schalters — aber es ist eine Übertragung von Besitz und keine
|
||||
// Kleinigkeit.
|
||||
const dasselbe = vorhanden.find(
|
||||
(s) => Number(s.port) === Number(x.port) && String(s.host) === String(x.adresse),
|
||||
);
|
||||
if (dasselbe) {
|
||||
if (!dasselbe.panel_name) {
|
||||
uebernehmenStmt.run(x.name, dasselbe.id);
|
||||
ergebnis.uebernommen.push(x.name);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
createGameserver({
|
||||
name: x.name,
|
||||
// OHNE gamedig-KENNUNG FRAGEN WIR DAS PANEL. Der Typ `panel` ist
|
||||
// unser dritter eigener neben `fivem` und `http` — siehe
|
||||
// `queryServer`. Wo gamedig kann, bleibt es bei gamedig: dessen
|
||||
// Antwort bringt Karte, Fassung und Ping mit, die das Panel nicht
|
||||
// liefert. Der neue Typ schliesst eine Luecke, er ersetzt nichts.
|
||||
type: x.gamedig || 'panel',
|
||||
host: x.adresse,
|
||||
port: x.port,
|
||||
address: `${x.adresse}:${x.port}`,
|
||||
query_url: '',
|
||||
// Die Herkunft. Nur was hier steht, wird später auch wieder
|
||||
// weggeräumt — der handgepflegte ATM10 ist davon nicht berührt.
|
||||
panel_name: x.name,
|
||||
});
|
||||
ergebnis.angelegt.push(x.name);
|
||||
}
|
||||
|
||||
for (const [name, s] of ausPanel) {
|
||||
if (gewollt.has(name)) continue;
|
||||
deleteGameserver(s.id);
|
||||
ergebnis.entfernt.push({ id: s.id, name, message_id: s.message_id ?? null });
|
||||
}
|
||||
return ergebnis;
|
||||
}
|
||||
|
||||
/** Das Status-Embed eines entfernten Servers mit wegräumen. */
|
||||
export async function embedWegraeumen(client, weg) {
|
||||
const channelId = getSetting('status_channel_id');
|
||||
if (!channelId) return;
|
||||
for (const e of weg) {
|
||||
if (!e.message_id) continue;
|
||||
const channel = await client.channels.fetch(channelId).catch(() => null);
|
||||
const message = await channel?.messages?.fetch(e.message_id).catch(() => null);
|
||||
await message?.delete().catch(() => {});
|
||||
}
|
||||
}
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
// Auskunft vom d4rk_gameserver-Panel.
|
||||
//
|
||||
// WARUM ÜBERHAUPT, wo der Monitor doch schon GameDig hat: weil GameDig einen
|
||||
// Server FRAGT, und ein Server, der noch startet, antwortet nicht. Für den
|
||||
// Monitor sieht das aus wie „offline" — und genau dann steht im Alarm-Kanal
|
||||
// eine Ausfallmeldung, obwohl niemand etwas kaputtgemacht hat.
|
||||
//
|
||||
// Ein Modpack braucht bis zum ersten Spieler Minuten; am ATM10 des Betreibers
|
||||
// gemessen sind es 306 Sekunden. Fünf Minuten Fehlalarm bei jedem Neustart.
|
||||
//
|
||||
// Das Panel weiß es besser, weil es den Container sieht statt ihn zu fragen:
|
||||
//
|
||||
// zustand was Docker sagt (running, exited, weg)
|
||||
// bereit ob das Fertig-Merkmal im Protokoll stand
|
||||
//
|
||||
// Damit lässt sich „läuft noch gar nicht" von „startet gerade" und von
|
||||
// „hängt" unterscheiden.
|
||||
//
|
||||
// ES IST ABSICHTLICH OPTIONAL. Ohne Adresse und Zeichen verhält sich der
|
||||
// Monitor exakt wie vorher, und wenn das Panel nicht antwortet, ebenfalls.
|
||||
// Eine Zusatzauskunft darf nie dazu führen, dass die Hauptauskunft ausfällt.
|
||||
|
||||
// Kurz zwischenspeichern: der Monitor fragt je Server einmal, und bei acht
|
||||
// Servern wären das acht Abrufe für dieselbe Antwort.
|
||||
let zwischen = { zeit: 0, items: null };
|
||||
const FRISCH_MS = 20_000;
|
||||
|
||||
/** Der ganze Panel-Status, oder null. Wirft nie. */
|
||||
export async function panelStatus() {
|
||||
// ERST HIER GELADEN und nicht oben im Modul: `runtime-settings` zieht die
|
||||
// Datenbank mit herein, und dann laesst sich keine der reinen Funktionen
|
||||
// unten mehr ohne better-sqlite3 pruefen. Eine Probe, die eine Datenbank
|
||||
// braucht, um eine Zeichenkette zu vergleichen, schreibt niemand.
|
||||
const { panelUrl, panelZeichen } = await import('./runtime-settings.js');
|
||||
const basis = panelUrl();
|
||||
const zeichen = panelZeichen();
|
||||
if (!basis || !zeichen) return null;
|
||||
|
||||
if (zwischen.items && Date.now() - zwischen.zeit < FRISCH_MS) return zwischen.items;
|
||||
|
||||
try {
|
||||
const steuer = AbortSignal.timeout(5000);
|
||||
const antwort = await fetch(`${basis.replace(/\/$/, '')}/api/oeffentlich/status`, {
|
||||
headers: { authorization: `Bearer ${zeichen}` },
|
||||
signal: steuer,
|
||||
});
|
||||
if (!antwort.ok) return null;
|
||||
const daten = await antwort.json();
|
||||
zwischen = { zeit: Date.now(), items: Array.isArray(daten.items) ? daten.items : [] };
|
||||
return zwischen.items;
|
||||
} catch {
|
||||
// Panel weg, Zeichen zurückgezogen, Netz hakt — der Monitor läuft
|
||||
// dann einfach ohne diese Zusatzauskunft weiter.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Den Panel-Eintrag zu einem Monitor-Server finden.
|
||||
*
|
||||
* ÜBER DEN PORT, nicht über den Namen. Namen sind Geschmackssache und stehen
|
||||
* in zwei Werkzeugen selten gleich; der Port ist eine Zahl, die beide kennen
|
||||
* und die nur einmal vergeben ist.
|
||||
*/
|
||||
export function panelEintrag(items, server) {
|
||||
if (!items?.length) return null;
|
||||
const port = Number(server?.port);
|
||||
if (Number.isFinite(port) && port > 0) {
|
||||
const treffer = items.find((x) => Number(x.port) === port);
|
||||
if (treffer) return treffer;
|
||||
}
|
||||
// Zweite Möglichkeit: gleicher Name, klein geschrieben. Hilft, wenn
|
||||
// jemand den Port im Monitor weggelassen hat.
|
||||
const name = String(server?.name ?? '').trim().toLowerCase();
|
||||
return items.find((x) => String(x.name).trim().toLowerCase() === name) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wie lange ein Start dauern darf, bevor er als hängend gilt.
|
||||
*
|
||||
* Gemessen: ATM10 braucht 306 Sekunden bis zum ersten Spieler. Fünfzehn
|
||||
* Minuten sind knapp das Dreifache — genug Luft für ein größeres Pack auf
|
||||
* kaltem Cache, und kurz genug, dass ein hängender Start noch am selben
|
||||
* Abend auffällt.
|
||||
*
|
||||
* DIESE GRENZE IST NÖTIG, sonst frisst die Anbindung genau den Alarm, für den
|
||||
* es sie gibt: `bereit` wird nie von allein wahr. Ein Server, der beim Start
|
||||
* hängt, bliebe für immer „startet gerade" — und für immer ohne Meldung.
|
||||
*/
|
||||
export const STARTGNADE_MS = 15 * 60_000;
|
||||
|
||||
/** Läuft der Container und ist er noch in der zugestandenen Startzeit? */
|
||||
export function startetNoch(eintrag) {
|
||||
if (eintrag?.zustand !== 'running' || eintrag.bereit) return false;
|
||||
// Ohne Startzeit lässt sich nichts begrenzen. Das passiert nur, wenn
|
||||
// Dockers Zeitstempel unlesbar ist — dieselbe Antwort, die uns gerade
|
||||
// „running" gesagt hat. Ein kaputtes Feld ist kein hängender Server,
|
||||
// deshalb hier Nachsicht.
|
||||
if (!eintrag.seit) return true;
|
||||
return Date.now() - eintrag.seit < STARTGNADE_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* War das Ende gewollt?
|
||||
*
|
||||
* 0 heißt sauber beendet, 143 ist SIGTERM (`docker stop`) und 137 SIGKILL
|
||||
* nach abgelaufener Frist — alle drei entstehen, wenn jemand im Panel auf
|
||||
* Stopp drückt. Jeder andere Code ist ein Absturz, und `oom` heißt: der
|
||||
* Speicher war zu knapp. Genau dafür ist der Alarm-Kanal da.
|
||||
*/
|
||||
export function gewolltAus(eintrag) {
|
||||
if (eintrag?.zustand !== 'exited') return false;
|
||||
if (eintrag.oom) return false;
|
||||
// `code` fehlt bei einem älteren Panel. Dann ist die Frage nicht
|
||||
// beantwortbar, und im Zweifel gilt der Ausfall als echt.
|
||||
return eintrag.code === 0 || eintrag.code === 143 || eintrag.code === 137;
|
||||
}
|
||||
|
||||
/**
|
||||
* Was im Embed stehen soll, wenn GameDig nichts erreicht hat.
|
||||
*
|
||||
* Gibt null zurück, wenn das Panel nichts beizutragen hat -- dann bleibt es
|
||||
* beim schlichten „Offline".
|
||||
*/
|
||||
export function zustandText(eintrag) {
|
||||
if (!eintrag) return null;
|
||||
|
||||
if (eintrag.zustand === 'running' && !eintrag.bereit) {
|
||||
const s = eintrag.seit ? Math.round((Date.now() - eintrag.seit) / 1000) : null;
|
||||
const dauer = s == null ? '' : s < 90 ? ` (seit ${s} s)` : ` (seit ${Math.round(s / 60)} min)`;
|
||||
// Nach der Gnadenfrist ist „startet gerade" keine Auskunft mehr,
|
||||
// sondern eine Beschwichtigung. Dann steht hier, was Sache ist.
|
||||
return startetNoch(eintrag)
|
||||
? `🟡 **Startet gerade**${dauer}`
|
||||
: `🟠 **Startet seit ${dauer.replace(/^ \(seit |\)$/g, '')} und ist nicht fertig**`;
|
||||
}
|
||||
|
||||
if (eintrag.zustand === 'exited') {
|
||||
if (eintrag.oom) return '🔴 **Vom Speicher erschlagen** (out of memory)';
|
||||
if (gewolltAus(eintrag)) return '⚫ **Gestoppt** (im Panel)';
|
||||
// Auch ohne `code` (älteres Panel) landet man hier — dann ohne Zahl.
|
||||
return eintrag.code == null
|
||||
? '🔴 **Beendet**'
|
||||
: `🔴 **Abgestürzt** (Code ${eintrag.code})`;
|
||||
}
|
||||
|
||||
if (eintrag.zustand === 'weg') return '🔴 **Container fehlt**';
|
||||
return null;
|
||||
}
|
||||
|
||||
/** „1,2 GB / 7 GB · 14 %" — nur wenn das Panel Werte hat. */
|
||||
export function lastText(eintrag) {
|
||||
if (!eintrag || eintrag.zustand !== 'running') return null;
|
||||
const teile = [];
|
||||
if (eintrag.ram) {
|
||||
const gb = (b) => `${(b / 1024 ** 3).toFixed(1)} GB`;
|
||||
teile.push(eintrag.ram_grenze ? `${gb(eintrag.ram)} / ${gb(eintrag.ram_grenze)}` : gb(eintrag.ram));
|
||||
}
|
||||
// Das Panel liefert Dockers Kern-Prozent (200 = zwei Kerne voll). Für eine
|
||||
// Statusmeldung ist der Anteil an der Maschine die verständlichere Zahl --
|
||||
// „187 %" liest sich wie ein Fehler.
|
||||
if (eintrag.cpu != null && eintrag.kerne) teile.push(`${(eintrag.cpu / eintrag.kerne).toFixed(1)} % CPU`);
|
||||
return teile.length ? teile.join(' · ') : null;
|
||||
}
|
||||
@@ -326,23 +326,6 @@ export function repoIgnored(repo) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* d4rk_gameserver-Panel — Adresse und Lesezeichen.
|
||||
*
|
||||
* BEIDES NUR HIER UND NICHT IN DER .env: der Betreiber soll es im Webinterface
|
||||
* eintragen können, so wie alles andere auch. Das Zeichen darf ausschließlich
|
||||
* lesen; selbst wenn es abhandenkommt, kann damit niemand einen Server stoppen.
|
||||
*
|
||||
* Leer = die Zusatzauskunft ist aus, und der Monitor verhält sich wie vorher.
|
||||
*/
|
||||
export function panelUrl() {
|
||||
return (getSetting('panel_url') || '').replace(/\/$/, '');
|
||||
}
|
||||
|
||||
export function panelZeichen() {
|
||||
return getSetting('panel_zeichen') || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Welche Protokoll-Meldungen überhaupt geschrieben werden.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user