Frontend, README und Konfigurationsvorlage
React-Oberfläche mit Übersicht, Match-Erstellung, Beitritt über Team- und Zuschauercodes, Teamkarten, visuellem Map-Veto, Serie und Serverpanel. README beschreibt Einrichtung, Umgebungsvariablen, Rollen, Veto-Abläufe, Serveradapter und CSTV-Zuschauerzugang. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
104aa1a67b
commit
568ccf9b09
24 files changed
+1938
-1
No files matched your search
@@ -0,0 +1,110 @@
|
||||
import { serverLabel } from '../lib/labels.js';
|
||||
import CopyField from './CopyField.jsx';
|
||||
|
||||
const RETRY = {
|
||||
start_failed: 'Server neu starten',
|
||||
failed: 'Server neu starten',
|
||||
stopped: 'Server neu starten',
|
||||
config_failed: 'Konfiguration wiederholen',
|
||||
ready: 'Konfiguration starten',
|
||||
};
|
||||
const ACTIVE = ['starting', 'ready', 'configuring', 'configured', 'config_failed', 'failed'];
|
||||
|
||||
export default function ServerPanel({ state, isOrg, act, busy }) {
|
||||
const { server } = state;
|
||||
const label = serverLabel(server);
|
||||
const closed = ['finished', 'cancelled'].includes(state.status);
|
||||
const sim = server?.simulated;
|
||||
const canRetry = server && !closed && RETRY[server.status] && (server.status !== 'ready' || state.status === 'server_setup');
|
||||
const hasNext = state.series.some((s) => s.number === (state.currentMapNumber ?? 0) + 1);
|
||||
|
||||
return (
|
||||
<section className={`panel server-panel tone-border-${label.tone}`}>
|
||||
<h2>Spielserver</h2>
|
||||
<div className="server-status">
|
||||
<span className={`status-dot tone-${label.tone} ${['starting', 'configuring'].includes(server?.status) ? 'pulse' : ''}`} />
|
||||
<strong className={`tone-${label.tone}`}>{label.text}</strong>
|
||||
</div>
|
||||
{server?.message && <p className="small">{server.message}</p>}
|
||||
|
||||
{sim && (
|
||||
<div className="alert alert-demo">
|
||||
Demo-Adapter aktiv: Es läuft kein echter CS2-Server. Start, Konfiguration und Verbindungsdaten sind simuliert.
|
||||
Wie die echte Anbindung eingerichtet wird, steht in der README.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!server && <p className="muted small">Für dieses Match wurde noch kein Server angefordert.</p>}
|
||||
{server && ['starting', 'ready'].includes(server.status) && state.status !== 'server_setup' && (
|
||||
<p className="muted small">
|
||||
{sim ? 'Der Start wird während des Vetos simuliert.' : 'Der Server fährt während des Vetos hoch.'} Die Verbindungsdaten erscheinen nach dem Veto.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{server?.connect && (
|
||||
<div className="connect">
|
||||
<h3>{sim ? 'Spieler-Verbindung (Demo-Beispiel, nicht verbindbar)' : 'Verbindung für Spieler'}</h3>
|
||||
<CopyField label="In die CS2-Konsole einfügen" value={server.connect.command} secret />
|
||||
{!sim && <a className="btn btn-primary" href={server.connect.steamUrl}>In CS2 verbinden</a>}
|
||||
<p className="small muted">Server {server.connect.host}:{server.connect.port}. Das Passwort ist nur für Spieler dieses Matches sichtbar.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{server?.gotv && (
|
||||
<div className="connect gotv">
|
||||
<h3>{sim ? 'CSTV/GOTV (Demo-Beispiel, nicht verbindbar)' : 'Zuschauen über CSTV (GOTV)'}</h3>
|
||||
<CopyField label="Für Stream und Kommentatoren" value={server.gotv.command} secret />
|
||||
<p className="small muted">
|
||||
Übertragung mit {server.gotv.delaySeconds} Sekunden Verzögerung gegen Ghosting. Zuschauer belegen keinen Spielerplatz.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{state.viewer.role === 'spectator' && server?.status === 'configured' && !server.gotv && (
|
||||
<p className="small muted">CSTV ist auf diesem Server deaktiviert.</p>
|
||||
)}
|
||||
|
||||
{isOrg && server && !closed && (
|
||||
<div className="org-controls">
|
||||
{canRetry && (
|
||||
<button className="btn btn-primary" disabled={!!busy} onClick={() => act('server/retry', null, 'Erneuter Versuch gestartet')}>
|
||||
{RETRY[server.status]}
|
||||
</button>
|
||||
)}
|
||||
{server.status === 'configured' && (
|
||||
<button className="btn" disabled={!!busy} onClick={() => act('server/warmup-end', null, 'Aufwärmphase beendet')}>
|
||||
Aufwärmphase beenden
|
||||
</button>
|
||||
)}
|
||||
{server.status === 'configured' && state.status === 'live' && hasNext && (
|
||||
<button className="btn" disabled={!!busy} onClick={() => act('series/next', null, 'Nächste Map wird geladen')}>
|
||||
Nächste Map laden
|
||||
</button>
|
||||
)}
|
||||
{ACTIVE.includes(server.status) && (
|
||||
<button className="btn btn-ghost" disabled={!!busy}
|
||||
onClick={() => window.confirm('Server wirklich stoppen?') && act('server/stop', null, 'Server gestoppt')}>
|
||||
Server stoppen
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isOrg && !closed && (
|
||||
<div className="org-controls">
|
||||
{['live', 'server_setup'].includes(state.status) && (
|
||||
<button className="btn" disabled={!!busy}
|
||||
onClick={() => window.confirm('Match abschließen und Server herunterfahren?') && act('finish', null, 'Match abgeschlossen')}>
|
||||
Match abschließen
|
||||
</button>
|
||||
)}
|
||||
<button className="btn btn-ghost danger" disabled={!!busy}
|
||||
onClick={() => window.confirm('Match abbrechen? Das kann nicht rückgängig gemacht werden.') && act('cancel', null, 'Match abgebrochen')}>
|
||||
Match abbrechen
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{server && ACTIVE.includes(server.status) && (
|
||||
<p className="small muted">Ungenutzte Server werden nach {server.idleTimeoutMinutes} Minuten automatisch heruntergefahren.</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user