Rollback: Runden aus CS2-Sicherungen zurückspielen (nur Admin)
Beim Konfigurieren aktiviert CS2 Mapveto die Rundensicherung des Servers (mp_backup_round_auto, Präfix mit Match-ID). Im Admin-Modus lassen sich die Sicherungen der laufenden Map auflisten und zurückspielen. Der Live-Stand wird auf die Runden bis zur Sicherung gekürzt. Organisatoren haben keinen Zugang zu den Rollback-Routen. Sicherheit: Dateinamen werden gegen Match-ID und Muster geprüft, damit keine fremden Dateien oder Befehle in die RCON-Zeile gelangen.
This commit is contained in:
1 parent
43ecb8a1ad
commit
3bfe087b7d
13 files changed
+327
-5
No files matched your search
@@ -99,7 +99,7 @@ export default function AdminPage() {
|
||||
|
||||
<div className="admin-list">
|
||||
{matches?.map((m) => (
|
||||
<MatchAdminCard key={m.id} m={m} run={run} busy={busy} />
|
||||
<MatchAdminCard key={m.id} m={m} run={run} busy={busy} token={token} reload={load} setNotice={setNotice} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,7 +137,56 @@ function AdminLogin({ onLogin }) {
|
||||
);
|
||||
}
|
||||
|
||||
function MatchAdminCard({ m, run, busy }) {
|
||||
/** Rollback: nur auf der Admin-Seite. Lädt die Sicherungen der aktuellen Map und spielt eine davon zurück. */
|
||||
function RollbackPanel({ matchId, token, reload, setNotice }) {
|
||||
const [points, setPoints] = useState(null);
|
||||
const [file, setFile] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await api(`/admin/matches/${matchId}/rollback-points`, { token });
|
||||
setPoints(res.points);
|
||||
setFile(res.points[0]?.file ?? '');
|
||||
} catch (err) {
|
||||
setNotice({ tone: 'error', text: err.message });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const rollback = async () => {
|
||||
const p = points.find((x) => x.file === file);
|
||||
if (!p || !window.confirm(`Match auf den Stand nach Runde ${p.round} zurückspielen? Spätere Runden dieser Map gehen verloren.`)) return;
|
||||
try {
|
||||
await api(`/admin/matches/${matchId}/rollback`, { method: 'POST', body: { file }, token });
|
||||
setNotice({ tone: 'ok', text: `Zurückgespielt auf Runde ${p.round}` });
|
||||
setPoints(null);
|
||||
reload();
|
||||
} catch (err) {
|
||||
setNotice({ tone: 'error', text: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rollback">
|
||||
<h3>Runde zurückspielen <span className="admin-badge">nur Admin</span></h3>
|
||||
{!points && <button className="btn btn-sm" onClick={load} disabled={loading}>{loading ? 'Lade …' : 'Sicherungen laden'}</button>}
|
||||
{points && points.length === 0 && <p className="small muted">Keine Sicherungen für diese Map gefunden.</p>}
|
||||
{points && points.length > 0 && (
|
||||
<div className="row">
|
||||
<select value={file} onChange={(e) => setFile(e.target.value)}>
|
||||
{points.map((p) => <option key={p.file} value={p.file}>Nach Runde {p.round}</option>)}
|
||||
</select>
|
||||
<button className="btn btn-sm btn-danger" onClick={rollback}>Zurückspielen</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MatchAdminCard({ m, run, busy, token, reload, setNotice }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const server = serverLabel(m.server);
|
||||
const closed = ['finished', 'cancelled'].includes(m.status);
|
||||
@@ -231,6 +280,10 @@ function MatchAdminCard({ m, run, busy }) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{m.status === 'live' && m.server?.status === 'configured' && (
|
||||
<RollbackPanel matchId={id} token={token} reload={reload} setNotice={setNotice} />
|
||||
)}
|
||||
|
||||
{m.veto && !m.veto.done && m.veto.current && (
|
||||
<p className="small">Veto: {m.teams[m.veto.current.team].name} ist am Zug ({m.veto.current.type}).</p>
|
||||
)}
|
||||
|
||||
@@ -506,3 +506,9 @@ input:focus, select:focus { border-color: var(--link); outline: none; }
|
||||
.admin-login { margin-top: 40px; }
|
||||
.danger { color: var(--error); }
|
||||
@media (max-width: 640px) { .admin-players { grid-template-columns: 1fr; } }
|
||||
|
||||
/* ---------- Rollback (nur Admin) ---------- */
|
||||
.rollback { border: 1px dashed var(--brand-red); border-radius: var(--radius); padding: 12px 14px; }
|
||||
.rollback h3 { margin: 0 0 8px; display: flex; align-items: center; gap: 10px; }
|
||||
.rollback select { min-width: 200px; }
|
||||
.admin-badge { font: 600 .75rem var(--body); color: #fff; background: var(--brand-red); padding: 2px 8px; border-radius: 3px; }
|
||||
Reference in new issue
Block a user