Ganze Abfrage-URL einfuegen statt drei Felder tippen
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

This commit is contained in:
2026-08-05 17:14:30 +02:00
parent 4e7fc99999
commit 8981eb91b8
5 changed files with 62 additions and 17 deletions
+1
View File
@@ -182,6 +182,7 @@ export default {
'server.free': 'frei', 'server.free': 'frei',
'server.full': 'voll', 'server.full': 'voll',
'server.whoIsOn': 'Wer gerade drauf ist', 'server.whoIsOn': 'Wer gerade drauf ist',
'server.nobody24h': 'In den letzten 24 Stunden war niemand drauf.',
'server.andMore': '+%s weitere', 'server.andMore': '+%s weitere',
'server.lastOnline': 'Zuletzt online: %s', 'server.lastOnline': 'Zuletzt online: %s',
'server.agoMin': 'vor %s min', 'server.agoMin': 'vor %s min',
+1
View File
@@ -181,6 +181,7 @@ export default {
'server.free': 'free', 'server.free': 'free',
'server.full': 'full', 'server.full': 'full',
'server.whoIsOn': 'Who is playing', 'server.whoIsOn': 'Who is playing',
'server.nobody24h': 'Nobody played in the last 24 hours.',
'server.andMore': '+%s more', 'server.andMore': '+%s more',
'server.lastOnline': 'Last online: %s', 'server.lastOnline': 'Last online: %s',
'server.agoMin': '%s min ago', 'server.agoMin': '%s min ago',
+10 -4
View File
@@ -75,6 +75,10 @@ function ServerCard({ s }) {
const [alleSpieler, setAlleSpieler] = useState(false); const [alleSpieler, setAlleSpieler] = useState(false);
const pct = s.online && s.max ? Math.round((s.players / s.max) * 100) : 0; const pct = s.online && s.max ? Math.round((s.players / s.max) * 100) : 0;
const stats = historyStats(s.history); const stats = historyStats(s.history);
// War in 24 h überhaupt jemand drauf? Wenn nicht, ist das Diagramm eine
// Nulllinie in hundert Pixeln Leere — das sieht nach Fehler aus, obwohl
// es keiner ist. Dann lieber ein Satz statt einer leeren Fläche.
const hatVerlauf = s.history.length > 1 && s.history.some((h) => (h.players ?? 0) > 0);
// Ab 80 % wird es eng, ab 100 % kommt keiner mehr rein — das soll man // Ab 80 % wird es eng, ab 100 % kommt keiner mehr rein — das soll man
// sehen, ohne den Balken mit der Zahl daneben zu vergleichen. // sehen, ohne den Balken mit der Zahl daneben zu vergleichen.
const fuellung = pct >= 100 ? 'voll' : pct >= 80 ? 'eng' : 'frei'; const fuellung = pct >= 100 ? 'voll' : pct >= 80 ? 'eng' : 'frei';
@@ -153,20 +157,22 @@ function ServerCard({ s }) {
</div> </div>
)} )}
{s.history.length > 1 && <HistoryChart history={s.history} max={s.max} />} {hatVerlauf ? (
{stats && ( <>
<HistoryChart history={s.history} max={s.max} />
<div className="srv-stats"> <div className="srv-stats">
<span>{t('server.players24h')}</span> <span>{t('server.players24h')}</span>
{/* Uptime steht jetzt am Balken darunter — zweimal dieselbe {/* Uptime steht jetzt am Balken darunter — zweimal dieselbe
Zahl in verschiedenen Zeiträumen verwirrt nur. */} Zahl in verschiedenen Zeiträumen verwirrt nur. */}
<span>{t('server.peak')} <b>{stats.peak}</b></span> <span>{t('server.peak')} <b>{stats.peak}</b></span>
</div> </div>
)}
{s.history.length > 1 && (
<div className="srv-achse"> <div className="srv-achse">
<span>{t('server.hours24')}</span> <span>{t('server.hours24')}</span>
<span>{t('server.now')}</span> <span>{t('server.now')}</span>
</div> </div>
</>
) : s.online && (
<p className="srv-leer">{t('server.nobody24h')}</p>
)} )}
<UptimeTrack buckets={s.uptime} /> <UptimeTrack buckets={s.uptime} />
+35 -3
View File
@@ -171,6 +171,33 @@ const BELIEBTE_SPIELE = [
]; ];
const GAMESEL_MAX = 60; const GAMESEL_MAX = 60;
/**
* Ganze Abfrage-URL eingefügt statt Host getippt? Dann Host, Port und Code
* daraus ziehen.
*
* Bei Farming Simulator ist genau das der Normalfall: der Hoster gibt einem
* eine fertige URL wie
* `http://1.2.3.4:8200/feed/dedicated-server-stats.xml?code=…`,
* und die drei Felder von Hand auseinanderzupflücken ist Arbeit, die niemand
* machen muss. Der Port steht mit drin — er ist nicht immer 8080.
*
* @returns {{host: string, port: string, token?: string} | null}
*/
function feedUrlZerlegen(text) {
const roh = String(text).trim();
if (!/^https?:\/\//i.test(roh)) return null;
let u;
try { u = new URL(roh); } catch { return null; }
if (!u.hostname) return null;
const code = u.searchParams.get('code') || u.searchParams.get('token') || '';
return {
host: u.hostname,
port: u.port || '',
// Ohne Code im Link den vorhandenen stehen lassen, statt ihn zu leeren
...(code ? { token: code } : {}),
};
}
/** Combobox für die Spiel-Auswahl — datalist ist nicht stylebar, das hier schon. /** Combobox für die Spiel-Auswahl — datalist ist nicht stylebar, das hier schon.
* Die Liste kommt aus gamedig selbst, damit sie mit jedem Update mitwächst; * Die Liste kommt aus gamedig selbst, damit sie mit jedem Update mitwächst;
* freie IDs bleiben trotzdem erlaubt. */ * freie IDs bleiben trotzdem erlaubt. */
@@ -832,6 +859,11 @@ export default function Settings({ me }) {
const serverNeedsUrl = ['fivem', 'http'].includes(serverDraft.type); const serverNeedsUrl = ['fivem', 'http'].includes(serverDraft.type);
const gewaehltesSpiel = games.find((g) => g.id === serverDraft.type) ?? null; const gewaehltesSpiel = games.find((g) => g.id === serverDraft.type) ?? null;
/** Ins Host-Feld getippt — oder die ganze Abfrage-URL eingefügt */
function hostEingeben(wert) {
const teile = feedUrlZerlegen(wert);
setServerDraft((d) => (teile ? { ...d, ...teile } : { ...d, host: wert }));
}
/** Spiel gewählt: Standard-Port vorschlagen, solange keiner drinsteht */ /** Spiel gewählt: Standard-Port vorschlagen, solange keiner drinsteht */
function spielWaehlen(type) { function spielWaehlen(type) {
const spiel = games.find((g) => g.id === type); const spiel = games.find((g) => g.id === type);
@@ -3332,14 +3364,14 @@ export default function Settings({ me }) {
<input type="text" placeholder={serverDraft.type === 'fivem' ? 'http://ip:30120' : 'https://dienst.example/health'} value={serverDraft.query_url} onChange={(e) => setServerDraft({ ...serverDraft, query_url: e.target.value })} /> <input type="text" placeholder={serverDraft.type === 'fivem' ? 'http://ip:30120' : 'https://dienst.example/health'} value={serverDraft.query_url} onChange={(e) => setServerDraft({ ...serverDraft, query_url: e.target.value })} />
) : ( ) : (
<> <>
<input type="text" placeholder="Host / IP" value={serverDraft.host} onChange={(e) => setServerDraft({ ...serverDraft, host: e.target.value })} /> <input type="text" placeholder="Host / IP — oder ganze Abfrage-URL einfügen" title="Du kannst hier auch die komplette Abfrage-URL einfügen; Host, Port und Code werden herausgezogen." value={serverDraft.host} onChange={(e) => hostEingeben(e.target.value)} />
<input type="text" style={{ flex: 'none', width: '6rem' }} placeholder={gewaehltesSpiel?.port ? `Port ${gewaehltesSpiel.port}` : 'Port'} value={serverDraft.port} onChange={(e) => setServerDraft({ ...serverDraft, port: e.target.value })} /> <input type="text" style={{ flex: 'none', width: '6rem' }} placeholder={gewaehltesSpiel?.port ? `Port ${gewaehltesSpiel.port}` : 'Port'} value={serverDraft.port} onChange={(e) => setServerDraft({ ...serverDraft, port: e.target.value })} />
</> </>
)} )}
</div> </div>
<p className="field-hint" style={{ marginTop: '.3rem' }}> <p className="field-hint" style={{ marginTop: '.3rem' }}>
{gewaehltesSpiel {gewaehltesSpiel
? <>Gewählt: <b>{gewaehltesSpiel.name}</b>{!gewaehltesSpiel.eigen && gewaehltesSpiel.port ? ` — Standard-Query-Port ${gewaehltesSpiel.port}` : ''}. Die Liste kommt direkt aus gamedig, {games.length} Einträge.</> ? <>Gewählt: <b>{gewaehltesSpiel.name}</b>{!gewaehltesSpiel.eigen && gewaehltesSpiel.port ? ` — Standard-Query-Port ${gewaehltesSpiel.port}, Hoster weichen oft davon ab` : ''}. Die Liste kommt direkt aus gamedig, {games.length} Einträge.</>
: <>Unbekannte ID sie wird trotzdem versucht. Alle bekannten stehen in der <a href="https://github.com/gamedig/node-gamedig/blob/master/GAMES_LIST.md" target="_blank" rel="noreferrer" style={{ textDecoration: 'underline' }}>gamedig-Liste</a>.</>} : <>Unbekannte ID sie wird trotzdem versucht. Alle bekannten stehen in der <a href="https://github.com/gamedig/node-gamedig/blob/master/GAMES_LIST.md" target="_blank" rel="noreferrer" style={{ textDecoration: 'underline' }}>gamedig-Liste</a>.</>}
</p> </p>
</div> </div>
@@ -3358,7 +3390,7 @@ export default function Settings({ me }) {
/> />
<p className="field-hint" style={{ marginTop: '.3rem' }}> <p className="field-hint" style={{ marginTop: '.3rem' }}>
{gewaehltesSpiel.id.startsWith('farmingsimulator') {gewaehltesSpiel.id.startsWith('farmingsimulator')
? <>Im Spiel heißt er <b>Code</b>. Steht im Webinterface des Servers unter <code>http://host:{gewaehltesSpiel.port ?? 8080}/settings.html</code> — er gilt nur für den eigenen Server.</> ? <>Im Spiel heißt er <b>Code</b> und steckt in der Abfrage-URL, die dein Hoster ausgibt: <code>/feed/dedicated-server-stats.xml?code=<b>DAS_HIER</b></code>. Am einfachsten die ganze URL oben ins Host-Feld einfügen Host, Port und Code werden herausgezogen.</>
: <>Steht im Webinterface des Servers. Ohne ihn liefert die Abfrage nichts und der Server stünde dauerhaft auf offline".</>} : <>Steht im Webinterface des Servers. Ohne ihn liefert die Abfrage nichts und der Server stünde dauerhaft auf offline".</>}
</p> </p>
</div> </div>
+5
View File
@@ -1835,6 +1835,11 @@ label.toggle {
color: var(--muted); background: var(--bg3); color: var(--muted); background: var(--bg3);
border: 1px solid var(--border); border-radius: 5px; padding: .22rem .5rem; border: 1px solid var(--border); border-radius: 5px; padding: .22rem .5rem;
} }
/* Steht anstelle des Verlaufs, wenn es nichts zu zeigen gibt */
.srv-leer {
font-family: var(--mono); font-size: .68rem; color: var(--muted2);
margin-top: 1rem; letter-spacing: .04em;
}
.srv-weg { .srv-weg {
font-family: var(--mono); font-size: .7rem; color: #f23f43; font-family: var(--mono); font-size: .7rem; color: #f23f43;
margin-top: .9rem; letter-spacing: .04em; margin-top: .9rem; letter-spacing: .04em;